From c432145c5648597b9412930c70a1d0e44a85c475 Mon Sep 17 00:00:00 2001 From: BelkacemYerfa Date: Sat, 17 Feb 2024 22:16:02 +0100 Subject: [PATCH 01/10] test : working on a custom api for the tree view comp --- packages/src/components/extension/tree-view/tree-view.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/src/components/extension/tree-view/tree-view.tsx b/packages/src/components/extension/tree-view/tree-view.tsx index 1b95fca..e99e082 100644 --- a/packages/src/components/extension/tree-view/tree-view.tsx +++ b/packages/src/components/extension/tree-view/tree-view.tsx @@ -10,6 +10,8 @@ import { useVirtualizer } from "@tanstack/react-virtual"; import { Button } from "@/components/ui/button"; import { CaretSortIcon } from "@radix-ui/react-icons"; +// TODO: Add the ability to add custom icons + export type TreeViewElement = { id: string; name: string; @@ -266,6 +268,8 @@ TreeItem.displayName = "TreeItem"; interface FolderComponentProps extends React.HTMLAttributes {} +// TODO: refactor the folder and file component api , to be used as custom component to build a primitive custom tree view + type FolderProps = { expendedItems?: string[]; handleSelect?: (id: string) => void; From e35268c9a12618cfe12e9c092e1db0df7fb54e50 Mon Sep 17 00:00:00 2001 From: BelkacemYerfa Date: Tue, 20 Feb 2024 11:36:43 +0100 Subject: [PATCH 02/10] change : the full version of the new api for the tree view --- packages/src/app/page.tsx | 6 +- packages/src/components/extension/model.tsx | 27 ++- .../extension/tree-view/tree-view.tsx | 225 ++++++++++++------ 3 files changed, 180 insertions(+), 78 deletions(-) diff --git a/packages/src/app/page.tsx b/packages/src/app/page.tsx index e9a22e0..0b31377 100644 --- a/packages/src/app/page.tsx +++ b/packages/src/app/page.tsx @@ -10,6 +10,7 @@ import { CarouselExample, BreadCrumbTest, FileUploaderTest, + TreeFileTest, } from "@/components/extension/model"; import { ModeToggle } from "@/components/toggle-theme"; @@ -19,14 +20,15 @@ export default function Home() { return (
{/* */} - {/* */} - {/* */} + {/* */} + {/* */} {/* */} {/* */} {/* */} {/* */} +
diff --git a/packages/src/components/extension/model.tsx b/packages/src/components/extension/model.tsx index 5c017c7..dfdef19 100644 --- a/packages/src/components/extension/model.tsx +++ b/packages/src/components/extension/model.tsx @@ -23,7 +23,7 @@ import { } from "@/components/ui/form"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; -import { TreeView } from "./tree-view/tree-view"; +import { File, Folder, Tree, TreeView } from "./tree-view/tree-view"; import { AspectRatio } from "@radix-ui/react-aspect-ratio"; import { CustomUploadInput, @@ -608,3 +608,28 @@ export const FileUploaderTest = () => { ); }; + +export const TreeFileTest = () => { + return ( + + + +

app.tsx

+
+ + +

input.tsx

+
+ +

button.tsx

+
+
+ + +

carousel.tsx

+
+
+
+
+ ); +}; diff --git a/packages/src/components/extension/tree-view/tree-view.tsx b/packages/src/components/extension/tree-view/tree-view.tsx index e99e082..7ec9217 100644 --- a/packages/src/components/extension/tree-view/tree-view.tsx +++ b/packages/src/components/extension/tree-view/tree-view.tsx @@ -4,7 +4,15 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { cn } from "@/lib/utils"; import * as AccordionPrimitive from "@radix-ui/react-accordion"; import { FileIcon, FolderIcon, FolderOpenIcon } from "lucide-react"; -import { forwardRef, useCallback, useEffect, useRef, useState } from "react"; +import { + createContext, + forwardRef, + useCallback, + useContext, + useEffect, + useRef, + useState, +} from "react"; import useResizeObserver from "use-resize-observer"; import { useVirtualizer } from "@tanstack/react-virtual"; import { Button } from "@/components/ui/button"; @@ -154,18 +162,20 @@ export const TreeView = ({ )} > - {getVirtualItems().map((element) => ( - - ))} + + {getVirtualItems().map((element) => ( + + ))} + - + + {getVirtualItems().map((element) => ( + + ))} + + ); }; @@ -224,12 +222,13 @@ export const TreeItem = forwardRef<
    {elements instanceof Array ? ( elements.map((element) => ( -
  • +
  • {element.children && element.children?.length > 0 ? ( - {element?.name} )} @@ -264,7 +262,6 @@ export const TreeItem = forwardRef< handleSelect={selectItem} isSelected={selectedId === elements?.id} > - {elements?.name}
  • @@ -296,7 +293,7 @@ export const useTree = () => { export const Tree = forwardRef< HTMLDivElement, React.HTMLAttributes ->(({ className, children }, ref) => { +>(({ className, children, ...props }, ref) => { const [selectedId, setSelectedId] = useState(null); const [expendedItems, setExpendedItems] = useState(null); @@ -322,12 +319,14 @@ export const Tree = forwardRef< width, } = useResizeObserver({}); + const style = props.style ?? { height, width }; + return ( -
    - +
    + {children}
    @@ -351,7 +350,7 @@ type FolderProps = { export const Folder = forwardRef< HTMLDivElement, FolderProps & React.HTMLAttributes ->(({ className, element, handleSelect, indicator, children }) => { +>(({ className, element, handleSelect, indicator, children }, ref) => { const name = element; const { handleExpand, expendedItems } = useTree(); @@ -427,7 +426,7 @@ export const File = forwardRef< ref={ref} aria-label="File" {...props} - className={`ml-5 px-1 ${ + className={`ml-5 pr-1 ${ selectedId === element && isSelectable ? " bg-muted rounded-md w-fit " : "" @@ -444,6 +443,7 @@ export const File = forwardRef< handleSelect?.(element ?? "") ?? selectItem(element ?? " ") } > + {children}
    From e8458c140ade21c071481102771d6bd8cfb56ba1 Mon Sep 17 00:00:00 2001 From: BelkacemYerfa Date: Tue, 20 Feb 2024 22:46:36 +0100 Subject: [PATCH 04/10] change : the latest complete rewrite for the tree view comp with the new api --- packages/src/components/extension/model.tsx | 234 +++++----- .../extension/tree-view/tree-view-api.tsx | 318 ++++++++++++++ .../extension/tree-view/tree-view.tsx | 409 ++---------------- 3 files changed, 481 insertions(+), 480 deletions(-) create mode 100644 packages/src/components/extension/tree-view/tree-view-api.tsx diff --git a/packages/src/components/extension/model.tsx b/packages/src/components/extension/model.tsx index 92596b4..f76ed52 100644 --- a/packages/src/components/extension/model.tsx +++ b/packages/src/components/extension/model.tsx @@ -23,7 +23,8 @@ import { } from "@/components/ui/form"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; -import { File, Folder, Tree, TreeView } from "./tree-view/tree-view"; +import { Tree, Folder, File } from "./tree-view/tree-view-api"; +import { TreeView } from "./tree-view/tree-view"; import { AspectRatio } from "@radix-ui/react-aspect-ratio"; import { CustomUploadInput, @@ -411,116 +412,6 @@ export const OtpTest = () => { ); }; -export const TreeViewTest = () => { - const elements = [ - { - id: "1", - isSelectable: true, - name: "Element 1", - children: [ - { - id: "2", - isSelectable: true, - name: "Element 2", - children: [ - { - id: "4", - isSelectable: true, - name: "Element 4", - children: [ - { - id: "5", - isSelectable: true, - name: "Element 5", - children: [ - { - id: "6", - isSelectable: true, - name: "Element 6", - children: [ - { - id: "7", - isSelectable: false, - name: "Element 7", - children: [], - }, - { - id: "21", - isSelectable: true, - name: "Element 21", - }, - ], - }, - { - id: "8", - isSelectable: true, - name: "Element 8", - }, - ], - }, - ], - }, - { - id: "3", - isSelectable: true, - name: "Element 3", - }, - ], - }, - ], - }, - - { - id: "9", - isSelectable: true, - name: "Element 9", - children: [ - { - id: "10", - isSelectable: true, - name: "Element 10", - children: [ - { - id: "11", - isSelectable: true, - name: "Element 11", - children: [], - }, - ], - }, - { - id: "12", - isSelectable: true, - name: "Element 12", - children: [ - { - id: "13", - isSelectable: true, - name: "Element 13", - children: [], - }, - ], - }, - ], - }, - - { - id: "20", - isSelectable: true, - name: "Element 20", - children: [], - }, - - // Add more elements as needed - ]; - - return ( -
    - -
    - ); -}; - export const BreadCrumbTest = () => { return ( @@ -611,7 +502,11 @@ export const FileUploaderTest = () => { export const TreeFileTest = () => { return ( - +

    app.tsx

    @@ -633,3 +528,118 @@ export const TreeFileTest = () => {
    ); }; + +export const TreeViewTest = () => { + const elements = [ + { + id: "1", + isSelectable: true, + name: "Element 1", + children: [ + { + id: "2", + isSelectable: true, + name: "Element 2", + children: [ + { + id: "4", + isSelectable: true, + name: "Element 4", + children: [ + { + id: "5", + isSelectable: true, + name: "Element 5", + children: [ + { + id: "6", + isSelectable: true, + name: "Element 6", + children: [ + { + id: "7", + isSelectable: false, + name: "Element 7", + children: [], + }, + { + id: "21", + isSelectable: true, + name: "Element 21", + }, + ], + }, + { + id: "8", + isSelectable: true, + name: "Element 8", + }, + ], + }, + ], + }, + { + id: "3", + isSelectable: true, + name: "Element 3", + }, + ], + }, + ], + }, + + { + id: "9", + isSelectable: true, + name: "Element 9", + children: [ + { + id: "10", + isSelectable: true, + name: "Element 10", + children: [ + { + id: "11", + isSelectable: true, + name: "Element 11", + children: [], + }, + ], + }, + { + id: "12", + isSelectable: true, + name: "Element 12", + children: [ + { + id: "13", + isSelectable: true, + name: "Element 13", + children: [], + }, + ], + }, + ], + }, + + { + id: "20", + isSelectable: true, + name: "Element 20", + children: [], + }, + + // Add more elements as needed + ]; + + return ( +
    + +
    + ); +}; diff --git a/packages/src/components/extension/tree-view/tree-view-api.tsx b/packages/src/components/extension/tree-view/tree-view-api.tsx new file mode 100644 index 0000000..8bf20e4 --- /dev/null +++ b/packages/src/components/extension/tree-view/tree-view-api.tsx @@ -0,0 +1,318 @@ +"use client"; + +import { ScrollArea } from "@/components/ui/scroll-area"; +import { cn } from "@/lib/utils"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { FileIcon, FolderIcon, FolderOpenIcon } from "lucide-react"; +import React, { + createContext, + forwardRef, + useCallback, + useContext, + useEffect, + useState, +} from "react"; +import useResizeObserver from "use-resize-observer"; +import { Button } from "@/components/ui/button"; +import { CaretSortIcon } from "@radix-ui/react-icons"; +import { TreeViewElement } from "./tree-view"; + +type TreeContextProps = { + selectedId: string | undefined; + expendedItems: string[] | undefined; + handleExpand: (id: string) => void; + selectItem: (id: string) => void; + setExpendedItems?: React.Dispatch>; +}; + +export const TreeContext = createContext(null); + +export const useTree = () => { + const context = useContext(TreeContext); + if (!context) { + throw new Error("useTree must be used within a TreeProvider"); + } + return context; +}; + +export const Tree = forwardRef< + HTMLDivElement, + { + initialSelectedId?: string; + elements?: TreeViewElement[]; + initialExpendedItems?: string[]; + } & React.HTMLAttributes +>( + ( + { + className, + elements, + initialSelectedId, + initialExpendedItems, + children, + ...props + }, + ref + ) => { + const [selectedId, setSelectedId] = useState( + initialSelectedId + ); + const [expendedItems, setExpendedItems] = useState( + initialExpendedItems + ); + + const selectItem = useCallback((id: string) => { + setSelectedId(id); + }, []); + + const handleExpand = useCallback((id: string) => { + setExpendedItems((prev) => { + if (prev?.includes(id)) { + return prev.filter((item) => item !== id); + } + return [...(prev ?? []), id]; + }); + }, []); + + const expandSpecificTargetedElements = useCallback( + (elements?: TreeViewElement[], selectId?: string) => { + if (!elements || !selectId) return; + const findParent = ( + currentElement: TreeViewElement, + currentPath: string[] = [] + ) => { + const newPath = [...currentPath, currentElement.id]; + if (currentElement.id === selectId) { + if (currentElement.isSelectable) { + setExpendedItems(newPath); + } else { + if (newPath.includes(currentElement.id)) { + newPath.pop(); + setExpendedItems(newPath); + return; + } + setExpendedItems(newPath); + } + } + + if ( + currentElement.isSelectable && + currentElement.children && + currentElement.children.length > 0 + ) { + currentElement.children.forEach((child) => { + findParent(child, newPath); + }); + } + }; + + elements.forEach((element) => { + findParent(element); + }); + }, + [] + ); + + useEffect(() => { + if (initialSelectedId) { + expandSpecificTargetedElements(elements, initialSelectedId); + } + }, []); + + const { + ref: containerRef, + height, + width, + } = useResizeObserver({}); + + const style = props.style ?? { height, width }; + + return ( + +
    + + {children} + +
    +
    + ); + } +); + +Tree.displayName = "Tree"; + +interface FolderComponentProps extends React.HTMLAttributes {} + +// TODO: refactor the folder and file component api , to be used as custom component to build a primitive custom tree view + +type FolderProps = { + expendedItems?: string[]; + indicator?: boolean; + element: string; + isSelectable?: boolean; +} & FolderComponentProps; + +export const Folder = forwardRef< + HTMLDivElement, + FolderProps & React.HTMLAttributes +>( + ( + { className, element, indicator, isSelectable = true, children, ...props }, + ref + ) => { + const { handleExpand, expendedItems } = useTree(); + + return ( + + + handleExpand(element)} + > + {expendedItems?.includes(element) ? ( + + ) : ( + + )} + {element ?? name} + + + {element && indicator && ( +
    + )} +
    {children}
    + + + + ); + } +); + +Folder.displayName = "Folder"; + +export const File = forwardRef< + HTMLButtonElement, + { + element?: string; + handleSelect?: (id: string) => void; + isSelectable?: boolean; + } & React.HTMLAttributes +>( + ( + { + element, + className, + handleSelect, + isSelectable = true, + children, + ...props + }, + ref + ) => { + const { selectedId, selectItem } = useTree(); + const isSelected = selectedId === element; + return ( + + ); + } +); + +File.displayName = "File"; + +export const CollapseButton = forwardRef< + HTMLButtonElement, + { + elements: TreeViewElement[]; + expandAll: boolean; + } & React.HTMLAttributes +>(({ className, elements, expandAll, ...props }, ref) => { + const { expendedItems, setExpendedItems } = useTree(); + + const expendAllTree = useCallback((elements: TreeViewElement[]) => { + const expandTree = (element: TreeViewElement) => { + if ( + element.isSelectable && + element.children && + element.children.length > 0 + ) { + setExpendedItems?.((prev) => [...(prev ?? []), element.name]); + element.children.forEach(expandTree); + } + }; + + elements.forEach(expandTree); + }, []); + + const closeAll = useCallback(() => { + setExpendedItems?.([]); + }, []); + + useEffect(() => { + if (expandAll) { + expendAllTree(elements); + } + }, [expandAll]); + + return ( + + ); +}); + +CollapseButton.displayName = "CollapseButton"; diff --git a/packages/src/components/extension/tree-view/tree-view.tsx b/packages/src/components/extension/tree-view/tree-view.tsx index 4defe17..029127f 100644 --- a/packages/src/components/extension/tree-view/tree-view.tsx +++ b/packages/src/components/extension/tree-view/tree-view.tsx @@ -1,22 +1,10 @@ "use client"; -import { ScrollArea } from "@/components/ui/scroll-area"; import { cn } from "@/lib/utils"; -import * as AccordionPrimitive from "@radix-ui/react-accordion"; -import { FileIcon, FolderIcon, FolderOpenIcon } from "lucide-react"; -import { - createContext, - forwardRef, - useCallback, - useContext, - useEffect, - useRef, - useState, -} from "react"; +import React, { forwardRef, useCallback, useRef } from "react"; import useResizeObserver from "use-resize-observer"; import { useVirtualizer } from "@tanstack/react-virtual"; -import { Button } from "@/components/ui/button"; -import { CaretSortIcon } from "@radix-ui/react-icons"; +import { Tree, Folder, File, CollapseButton } from "./tree-view-api"; // TODO: Add the ability to add custom icons @@ -53,95 +41,6 @@ export const TreeView = ({ expandAll = false, indicator = false, }: TreeViewProps) => { - const [selectedId, setSelectIds] = useState( - initialSelectedId - ); - - const [expendedItems, setExpendedItems] = useState( - initialExpendedItems - ); - - const selectItem = useCallback((id: string) => { - setSelectIds(id); - }, []); - - const handleExpand = useCallback((id: string) => { - setExpendedItems((prev) => { - if (prev?.includes(id)) { - return prev.filter((item) => item !== id); - } - return [...(prev ?? []), id]; - }); - }, []); - - const expendAllTree = useCallback((elements: TreeViewElement[]) => { - const expandTree = (element: TreeViewElement) => { - if ( - element.isSelectable && - element.children && - element.children.length > 0 - ) { - setExpendedItems((prev) => [...(prev ?? []), element.id]); - element.children.forEach(expandTree); - } - }; - - elements.forEach(expandTree); - }, []); - - const expandSpecificTargetedElements = useCallback( - (elements: TreeViewElement[], selectId: string) => { - const findParent = ( - currentElement: TreeViewElement, - currentPath: string[] = [] - ) => { - const newPath = [...currentPath, currentElement.id]; - console.log(newPath, currentElement.isSelectable, selectId); - if (currentElement.id === selectId) { - if (currentElement.isSelectable) { - setExpendedItems(newPath); - } else { - if (newPath.includes(currentElement.id)) { - newPath.pop(); - setExpendedItems(newPath); - return; - } - setExpendedItems(newPath); - } - } - - if ( - currentElement.isSelectable && - currentElement.children && - currentElement.children.length > 0 - ) { - currentElement.children.forEach((child) => { - findParent(child, newPath); - }); - } - }; - - elements.forEach((element) => { - findParent(element); - }); - }, - [] - ); - - const closeAll = useCallback(() => { - setExpendedItems([]); - }, []); - - useEffect(() => { - if (expandAll) { - expendAllTree(elements); - return; - } - if (initialSelectedId) { - expandSpecificTargetedElements(elements, initialSelectedId); - } - }, []); - const containerRef = useRef(null); const { getVirtualItems, getTotalSize } = useVirtualizer({ count: elements.length, @@ -161,32 +60,21 @@ export const TreeView = ({ className )} > - + {getVirtualItems().map((element) => ( ))} + -
    ); }; @@ -196,259 +84,44 @@ TreeView.displayName = "TreeView"; export const TreeItem = forwardRef< HTMLUListElement, { - expendedItems?: string[]; - selectedId?: string; elements?: TreeViewElement[] | TreeViewElement; - handleSelect: (id: string) => void; - selectItem: (id: string) => void; indicator?: boolean; } & React.HTMLAttributes ->( - ( - { - className, - elements, - selectItem, - handleSelect, - expendedItems, - selectedId, - indicator, - ...props - }, - ref - ) => { - console.log("expendedItems", expendedItems, "selectedId", selectedId); - return ( -
      - {elements instanceof Array ? ( - elements.map((element) => ( -
    • - {element.children && element.children?.length > 0 ? ( - - - - ) : ( - (({ className, elements, indicator, ...props }, ref) => { + return ( +
        + {elements instanceof Array ? ( + elements.map((element) => ( +
      • + {element.children && element.children?.length > 0 ? ( + + - {element?.name} - - )} -
      • - )) - ) : ( -
      • - - {elements?.name} - + aria-label={`folder ${element.name}`} + elements={element.children} + indicator={indicator} + /> + + ) : ( + + {element?.name} + + )}
      • - )} -
      - ); - } -); - -TreeItem.displayName = "TreeItem"; - -type TreeContextProps = { - selectedId: string | null; - expendedItems: string[] | null; - handleExpand: (id: string) => void; - selectItem: (id: string) => void; -}; - -export const TreeContext = createContext(null); - -export const useTree = () => { - const context = useContext(TreeContext); - if (!context) { - throw new Error("useTree must be used within a TreeProvider"); - } - return context; -}; - -export const Tree = forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, children, ...props }, ref) => { - const [selectedId, setSelectedId] = useState(null); - const [expendedItems, setExpendedItems] = useState(null); - - const selectItem = useCallback((id: string) => { - setSelectedId(id); - }, []); - - const handleExpand = useCallback( - (id: string) => { - setExpendedItems((prev) => { - if (prev?.includes(id)) { - return prev.filter((item) => item !== id); - } - return [...(prev ?? []), id]; - }); - console.log("expendedItems", expendedItems); - }, - [expendedItems] - ); - const { - ref: containerRef, - height, - width, - } = useResizeObserver({}); - - const style = props.style ?? { height, width }; - - return ( - -
      - - {children} - -
      -
      - ); -}); - -Tree.displayName = "Tree"; - -interface FolderComponentProps extends React.HTMLAttributes {} - -// TODO: refactor the folder and file component api , to be used as custom component to build a primitive custom tree view - -type FolderProps = { - expendedItems?: string[]; - handleSelect?: (id: string) => void; - indicator?: boolean; - element: string; -} & FolderComponentProps; - -export const Folder = forwardRef< - HTMLDivElement, - FolderProps & React.HTMLAttributes ->(({ className, element, handleSelect, indicator, children }, ref) => { - const name = element; - - const { handleExpand, expendedItems } = useTree(); - - return ( - - - { - handleExpand(name as string); - }} - > - {expendedItems?.includes(element) ? ( - - ) : ( - - )} - {element ?? name} - - - {element && indicator && ( -
      - )} -
      {children}
      - - - + )) + ) : ( +
    • + + {elements?.name} + +
    • + )} +
    ); }); -Folder.displayName = "Folder"; - -export const File = forwardRef< - HTMLButtonElement, - { - element?: string; - handleSelect?: (id: string) => void; - isSelected?: boolean; - isSelectable?: boolean; - } & React.HTMLAttributes ->( - ( - { - element, - className, - handleSelect, - isSelected, - isSelectable = true, - children, - ...props - }, - ref - ) => { - const { selectedId, selectItem } = useTree(); - return ( - - ); - } -); - -File.displayName = "File"; +TreeItem.displayName = "TreeItem"; From 626f2ecfb229aeafabc81fb28200b85901c4a8d8 Mon Sep 17 00:00:00 2001 From: BelkacemYerfa Date: Wed, 21 Feb 2024 15:52:05 +0100 Subject: [PATCH 05/10] fix : refactor some css parts, change: the new api tree view is stable --- packages/src/app/page.tsx | 16 +++- packages/src/components/extension/model.tsx | 56 +++++++++-- .../extension/tree-view/tree-view-api.tsx | 96 +++++++++---------- .../extension/tree-view/tree-view.tsx | 1 + 4 files changed, 112 insertions(+), 57 deletions(-) diff --git a/packages/src/app/page.tsx b/packages/src/app/page.tsx index 0b31377..9ee8a7c 100644 --- a/packages/src/app/page.tsx +++ b/packages/src/app/page.tsx @@ -25,10 +25,22 @@ export default function Home() { {/* */} {/* */} - {/* */} {/* */} - +
    +
    +

    + Full Built in {"<"}TreeView /{">"}{" "} +

    + +
    +
    +

    + Full Built in {"<"}Tree/{">"} Api{" "} +

    + +
    +
    diff --git a/packages/src/components/extension/model.tsx b/packages/src/components/extension/model.tsx index f76ed52..d98ec19 100644 --- a/packages/src/components/extension/model.tsx +++ b/packages/src/components/extension/model.tsx @@ -23,7 +23,7 @@ import { } from "@/components/ui/form"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; -import { Tree, Folder, File } from "./tree-view/tree-view-api"; +import { Tree, Folder, File, CollapseButton } from "./tree-view/tree-view-api"; import { TreeView } from "./tree-view/tree-view"; import { AspectRatio } from "@radix-ui/react-aspect-ratio"; import { @@ -501,17 +501,60 @@ export const FileUploaderTest = () => { }; export const TreeFileTest = () => { + const elements = [ + { + id: "1", + isSelectable: true, + name: "src", + children: [ + { + id: "2", + isSelectable: true, + name: "app.tsx", + }, + { + id: "3", + isSelectable: true, + name: "components", + children: [ + { + id: "4", + isSelectable: true, + name: "input.tsx", + }, + { + id: "5", + isSelectable: true, + name: "button.tsx", + }, + ], + }, + { + id: "6", + isSelectable: true, + name: "ui", + children: [ + { + id: "7", + isSelectable: true, + name: "carousel.tsx", + }, + ], + }, + ], + }, + ]; return ( - +

    app.tsx

    - +

    input.tsx

    @@ -525,6 +568,7 @@ export const TreeFileTest = () => {
    +
    ); }; diff --git a/packages/src/components/extension/tree-view/tree-view-api.tsx b/packages/src/components/extension/tree-view/tree-view-api.tsx index 8bf20e4..aa6ce13 100644 --- a/packages/src/components/extension/tree-view/tree-view-api.tsx +++ b/packages/src/components/extension/tree-view/tree-view-api.tsx @@ -20,6 +20,7 @@ import { TreeViewElement } from "./tree-view"; type TreeContextProps = { selectedId: string | undefined; expendedItems: string[] | undefined; + indicator: boolean; handleExpand: (id: string) => void; selectItem: (id: string) => void; setExpendedItems?: React.Dispatch>; @@ -41,6 +42,7 @@ export const Tree = forwardRef< initialSelectedId?: string; elements?: TreeViewElement[]; initialExpendedItems?: string[]; + indicator?: boolean; } & React.HTMLAttributes >( ( @@ -50,6 +52,7 @@ export const Tree = forwardRef< initialSelectedId, initialExpendedItems, children, + indicator = true, ...props }, ref @@ -117,7 +120,7 @@ export const Tree = forwardRef< if (initialSelectedId) { expandSpecificTargetedElements(elements, initialSelectedId); } - }, []); + }, [initialSelectedId, elements]); const { ref: containerRef, @@ -135,10 +138,11 @@ export const Tree = forwardRef< handleExpand, selectItem, setExpendedItems, + indicator, }} > -
    - +
    + {children}
    @@ -163,50 +167,46 @@ type FolderProps = { export const Folder = forwardRef< HTMLDivElement, FolderProps & React.HTMLAttributes ->( - ( - { className, element, indicator, isSelectable = true, children, ...props }, - ref - ) => { - const { handleExpand, expendedItems } = useTree(); +>(({ className, element, isSelectable = true, children, ...props }, ref) => { + const { handleExpand, expendedItems, indicator } = useTree(); - return ( - + - handleExpand(element)} > - handleExpand(element)} - > - {expendedItems?.includes(element) ? ( - - ) : ( - - )} - {element ?? name} - - - {element && indicator && ( -
    - )} -
    {children}
    - - - - ); - } -); + {expendedItems?.includes(element) ? ( + + ) : ( + + )} + {element} + + + {element && indicator && ( +
    + )} +
    {children}
    + + + + ); +}); Folder.displayName = "Folder"; @@ -238,10 +238,8 @@ export const File = forwardRef< ref={ref} aria-label="File" {...props} - className={`ml-5 pr-1 ${ - selectedId === element && isSelectable - ? " bg-muted rounded-md w-fit " - : "" + className={`pr-1 rounded-md w-fit duration-300 ease-in-out ${ + isSelected && isSelectable ? "bg-muted" : "" } `} >
    >(({ className, elements, expandAll, ...props }, ref) => { const { expendedItems, setExpendedItems } = useTree(); diff --git a/packages/src/components/extension/tree-view/tree-view.tsx b/packages/src/components/extension/tree-view/tree-view.tsx index 029127f..e187f7e 100644 --- a/packages/src/components/extension/tree-view/tree-view.tsx +++ b/packages/src/components/extension/tree-view/tree-view.tsx @@ -42,6 +42,7 @@ export const TreeView = ({ indicator = false, }: TreeViewProps) => { const containerRef = useRef(null); + const { getVirtualItems, getTotalSize } = useVirtualizer({ count: elements.length, getScrollElement: () => containerRef.current, From 854cc7ea1f5777125e0af6d85935a183842c0dae Mon Sep 17 00:00:00 2001 From: BelkacemYerfa Date: Thu, 22 Feb 2024 20:25:22 +0100 Subject: [PATCH 06/10] change : support the custom keyboard navigation with the nested accordions --- packages/src/app/page.tsx | 16 +-- packages/src/components/extension/model.tsx | 49 ++++++++ .../extension/tree-view/tree-view-api.tsx | 115 ++++++++++-------- .../extension/tree-view/tree-view.tsx | 17 +-- 4 files changed, 121 insertions(+), 76 deletions(-) diff --git a/packages/src/app/page.tsx b/packages/src/app/page.tsx index 9ee8a7c..ae1e6b9 100644 --- a/packages/src/app/page.tsx +++ b/packages/src/app/page.tsx @@ -1,17 +1,5 @@ /* import { MultiSelect } from "@/components/extension/fancy-multi-select/multi-select"; */ -import { BreadCrumb } from "@/components/extension/breadcrumb/bread-crumb"; -import { - ImageUpload, - Model, - OtpTest, - TreeViewTest, - Commander, - CommanderUsingUseState, - CarouselExample, - BreadCrumbTest, - FileUploaderTest, - TreeFileTest, -} from "@/components/extension/model"; +import { TreeViewTest, TreeFileTest } from "@/components/extension/model"; import { ModeToggle } from "@/components/toggle-theme"; @@ -36,7 +24,7 @@ export default function Home() {

    - Full Built in {"<"}Tree/{">"} Api{" "} + Full Built in {"<"}Tree /{">"} Api{" "}

    diff --git a/packages/src/components/extension/model.tsx b/packages/src/components/extension/model.tsx index d98ec19..a928748 100644 --- a/packages/src/components/extension/model.tsx +++ b/packages/src/components/extension/model.tsx @@ -46,6 +46,7 @@ import { FileInput, } from "./file-uploader/file-uploader"; import { Paperclip } from "lucide-react"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; export type FilePreview = { file: File; @@ -527,6 +528,18 @@ export const TreeFileTest = () => { isSelectable: true, name: "button.tsx", }, + { + id: "20", + isSelectable: true, + name: "pages", + children: [ + { + id: "21", + isSelectable: true, + name: "interface.ts", + }, + ], + }, ], }, { @@ -549,6 +562,7 @@ export const TreeFileTest = () => { className="rounded-md outline h-60 w-96 outline-1 outline-muted overflow-hidden py-1" initialExpendedItems={["src", "components"]} initialSelectedId="button.tsx" + elements={elements} > @@ -561,6 +575,11 @@ export const TreeFileTest = () => {

    button.tsx

    + + +

    interface.ts

    +
    +
    @@ -687,3 +706,33 @@ export const TreeViewTest = () => {
    ); }; + +export const AccordionTest = () => { + return ( +
    + + + Trigger 1 + Content 1 + + + Trigger 2 + + + + + Trigger 2.1 + + + + + + + + Trigger 3 + Content 3 + + +
    + ); +}; diff --git a/packages/src/components/extension/tree-view/tree-view-api.tsx b/packages/src/components/extension/tree-view/tree-view-api.tsx index aa6ce13..4f648c0 100644 --- a/packages/src/components/extension/tree-view/tree-view-api.tsx +++ b/packages/src/components/extension/tree-view/tree-view-api.tsx @@ -143,7 +143,17 @@ export const Tree = forwardRef< >
    - {children} + + setExpendedItems((prev) => [...(prev ?? []), value[0]]) + } + > + {children} +
    @@ -155,8 +165,6 @@ Tree.displayName = "Tree"; interface FolderComponentProps extends React.HTMLAttributes {} -// TODO: refactor the folder and file component api , to be used as custom component to build a primitive custom tree view - type FolderProps = { expendedItems?: string[]; indicator?: boolean; @@ -168,43 +176,46 @@ export const Folder = forwardRef< HTMLDivElement, FolderProps & React.HTMLAttributes >(({ className, element, isSelectable = true, children, ...props }, ref) => { - const { handleExpand, expendedItems, indicator } = useTree(); + const { handleExpand, expendedItems, indicator, setExpendedItems } = + useTree(); return ( - - handleExpand(element)} > - handleExpand(element)} + {expendedItems?.includes(element) ? ( + + ) : ( + + )} + {element} + + + {element && indicator && ( +
    + )} + { + setExpendedItems?.((prev) => [...(prev ?? []), value[0]]); + }} > - {expendedItems?.includes(element) ? ( - - ) : ( - - )} - {element} - - - {element && indicator && ( -
    - )} -
    {children}
    - - - + {children} + + + ); }); @@ -213,7 +224,7 @@ Folder.displayName = "Folder"; export const File = forwardRef< HTMLButtonElement, { - element?: string; + element: string; handleSelect?: (id: string) => void; isSelectable?: boolean; } & React.HTMLAttributes @@ -232,29 +243,25 @@ export const File = forwardRef< const { selectedId, selectItem } = useTree(); const isSelected = selectedId === element; return ( - + + ); } ); diff --git a/packages/src/components/extension/tree-view/tree-view.tsx b/packages/src/components/extension/tree-view/tree-view.tsx index e187f7e..0b9fe29 100644 --- a/packages/src/components/extension/tree-view/tree-view.tsx +++ b/packages/src/components/extension/tree-view/tree-view.tsx @@ -93,9 +93,9 @@ export const TreeItem = forwardRef<
      {elements instanceof Array ? ( elements.map((element) => ( -
    • +
      {element.children && element.children?.length > 0 ? ( - + {element?.name} )} -
    • +
    )) ) : ( -
  • - - {elements?.name} - -
  • + + {elements?.name} + )}
); From 262e018291e8b23e3e5b8f256f067838cf08971c Mon Sep 17 00:00:00 2001 From: BelkacemYerfa Date: Fri, 23 Feb 2024 16:36:15 +0100 Subject: [PATCH 07/10] change : new api for the multi-select component --- packages/package-lock.json | 28 +- packages/package.json | 4 +- packages/src/app/page.tsx | 30 +- .../fancy-multi-select/multi-select-api.tsx | 258 ++++++++++++++++++ .../fancy-multi-select/multi-select.tsx | 9 +- packages/src/components/extension/model.tsx | 123 +++++++-- 6 files changed, 399 insertions(+), 53 deletions(-) create mode 100644 packages/src/components/extension/fancy-multi-select/multi-select-api.tsx diff --git a/packages/package-lock.json b/packages/package-lock.json index a303fef..246d1e2 100644 --- a/packages/package-lock.json +++ b/packages/package-lock.json @@ -24,8 +24,8 @@ "clsx": "^2.1.0", "cmdk": "^0.2.0", "date-fns": "^3.3.1", - "embla-carousel": "^8.0.0-rc17", - "embla-carousel-react": "^8.0.0-rc17", + "embla-carousel": "^8.0.0", + "embla-carousel-react": "^8.0.0", "lucide-react": "^0.316.0", "next": "14.1.0", "next-themes": "^0.2.1", @@ -2475,28 +2475,28 @@ "dev": true }, "node_modules/embla-carousel": { - "version": "8.0.0-rc17", - "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.0.0-rc17.tgz", - "integrity": "sha512-evF49b88VOitvqFtlvhvKVSu96Y8A+QSFdhok87Bfm8R7OYuk95FT+o8+M1GQLi/EhGDUlT193HTVAR0Wt2neQ==" + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.0.0.tgz", + "integrity": "sha512-ecixcyqS6oKD2nh5Nj5MObcgoSILWNI/GtBxkidn5ytFaCCmwVHo2SecksaQZHcARMMpIR2dWOlSIdA1LkZFUA==" }, "node_modules/embla-carousel-react": { - "version": "8.0.0-rc17", - "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.0.0-rc17.tgz", - "integrity": "sha512-x4aFprwFB+PQO9EsHHZsrDxARb0uYNBYn9mr5oDFdBdPez4M8G1r5yidWbUcT9pNUc8AQXC9sGzlfauBfBxVOw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.0.0.tgz", + "integrity": "sha512-qT0dii8ZwoCtEIBE6ogjqU2+5IwnGfdt2teKjCzW88JRErflhlCpz8KjWnW8xoRZOP8g0clRtsMEFoAgS/elfA==", "dependencies": { - "embla-carousel": "8.0.0-rc17", - "embla-carousel-reactive-utils": "8.0.0-rc17" + "embla-carousel": "8.0.0", + "embla-carousel-reactive-utils": "8.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.1 || ^18.0.0" } }, "node_modules/embla-carousel-reactive-utils": { - "version": "8.0.0-rc17", - "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.0.0-rc17.tgz", - "integrity": "sha512-eluEOK/u5HdjYaTLC4bUG3iTCnyX7RsYix3il0aH4ZECOKa5fS+pVK2vrM17Mgw6C5Hyjcr3r3lfJtGerVzVsQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.0.0.tgz", + "integrity": "sha512-JCw0CqCXI7tbHDRogBb9PoeMLyjEC1vpN0lDOzUjmlfVgtfF+ffLaOK8bVtXVUEbNs/3guGe3NSzA5J5aYzLzw==", "peerDependencies": { - "embla-carousel": "8.0.0-rc17" + "embla-carousel": "8.0.0" } }, "node_modules/emoji-regex": { diff --git a/packages/package.json b/packages/package.json index 845c285..42db997 100644 --- a/packages/package.json +++ b/packages/package.json @@ -25,8 +25,8 @@ "clsx": "^2.1.0", "cmdk": "^0.2.0", "date-fns": "^3.3.1", - "embla-carousel": "^8.0.0-rc17", - "embla-carousel-react": "^8.0.0-rc17", + "embla-carousel": "^8.0.0", + "embla-carousel-react": "^8.0.0", "lucide-react": "^0.316.0", "next": "14.1.0", "next-themes": "^0.2.1", diff --git a/packages/src/app/page.tsx b/packages/src/app/page.tsx index ae1e6b9..f70a4cc 100644 --- a/packages/src/app/page.tsx +++ b/packages/src/app/page.tsx @@ -1,21 +1,36 @@ /* import { MultiSelect } from "@/components/extension/fancy-multi-select/multi-select"; */ -import { TreeViewTest, TreeFileTest } from "@/components/extension/model"; +import { BreadCrumb } from "@/components/extension/breadcrumb/bread-crumb"; +import { + ImageUpload, + Model, + OtpTest, + TreeViewTest, + Commander, + CommanderUsingUseState, + CarouselExample, + BreadCrumbTest, + FileUploaderTest, + TreeFileTest, + MultiSelectorComp, +} from "@/components/extension/model"; import { ModeToggle } from "@/components/toggle-theme"; //provide the set of api to the component that allow to build the ui examples export default function Home() { return ( -
+
{/* */} - {/* */} - {/* */} - {/* */} + {/* + */} + + {/* */} {/* */} {/* */} -
+ {} + {/*

Full Built in {"<"}TreeView /{">"}{" "} @@ -28,7 +43,8 @@ export default function Home() {

-
+
*/} +
diff --git a/packages/src/components/extension/fancy-multi-select/multi-select-api.tsx b/packages/src/components/extension/fancy-multi-select/multi-select-api.tsx new file mode 100644 index 0000000..c8a1911 --- /dev/null +++ b/packages/src/components/extension/fancy-multi-select/multi-select-api.tsx @@ -0,0 +1,258 @@ +"use client"; + +import { Badge } from "@/components/ui/badge"; +import { + Command, + CommandItem, + CommandEmpty, + CommandList, +} from "@/components/ui/command"; +import { cn } from "@/lib/utils"; +import { Command as CommandPrimitive } from "cmdk"; +import { X as RemoveIcon, CheckCheck } from "lucide-react"; +import React, { + KeyboardEvent, + createContext, + forwardRef, + useCallback, + useContext, + useRef, + useState, +} from "react"; + +type MultiSelectorProps = { + value: string[]; + onValueChange: (value: string[]) => void; +} & React.HTMLAttributes; + +type MultiSelectContextProps = { + value: string[]; + onValueChange: (value: any) => void; + open: boolean; + setOpen: (value: boolean) => void; + inputValue: string; + setInputValue: React.Dispatch>; + inputRef?: React.RefObject; +}; + +const MultiSelectContext = createContext(null); + +const useMultiSelect = () => { + const context = useContext(MultiSelectContext); + if (!context) { + throw new Error("useMultiSelect must be used within MultiSelectProvider"); + } + return context; +}; + +const MultiSelector = ({ + value, + onValueChange, + className, + children, +}: MultiSelectorProps) => { + const inputRef = useRef(null); + const [inputValue, setInputValue] = useState(""); + const [open, setOpen] = useState(false); + + const onValueChangeHandler = useCallback( + (val: string) => { + if (value.includes(val)) { + onValueChange(value.filter((item) => item !== val)); + } else { + onValueChange([...value, val]); + } + }, + [value] + ); + + const removeOptionWithBackspace = useCallback( + (e: KeyboardEvent) => { + if ((e.key === "Backspace" || e.key === "Delete") && value.length > 0) { + if (inputValue.length === 0) { + onValueChange( + value.filter((item) => item !== value[value.length - 1]) + ); + } + } else if (e.key === "Enter") { + setOpen(true); + } else if (e.key === "Escape") { + setOpen(false); + } + }, + [value, inputValue] + ); + + return ( + + + {children} + + + ); +}; + +const MultiSelectorTrigger = forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, children }, ref) => { + const { value, onValueChange } = useMultiSelect(); + const mousePreventDefault = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + }, []); + return ( +
+ {value.map((item) => ( + + {item} + + + ))} + {children} +
+ ); +}); + +MultiSelectorTrigger.displayName = "MultiSelectorTrigger"; + +const MultiSelectorInput = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ ...props }, ref) => { + const { setOpen, inputValue, inputRef, setInputValue } = useMultiSelect(); + return ( + setOpen(false)} + onFocus={() => setOpen(true)} + className="ml-2 bg-transparent outline-none placeholder:text-muted-foreground flex-1" + /> + ); +}); + +MultiSelectorInput.displayName = "MultiSelectorInput"; + +const MultiSelectorContent = forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ children }, ref) => { + const { open } = useMultiSelect(); + return ( +
+ {open && children} +
+ ); +}); + +MultiSelectorContent.displayName = "MultiSelectorContent"; + +const MultiSelectorList = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children }, ref) => { + return ( + + {children} + + No results found + + + ); +}); + +MultiSelectorList.displayName = "MultiSelectorList"; + +const MultiSelectorItem = forwardRef< + React.ElementRef, + { value: string } & React.ComponentPropsWithoutRef< + typeof CommandPrimitive.Item + > +>(({ className, value, children, ...props }, ref) => { + const { value: Options, onValueChange, setInputValue } = useMultiSelect(); + + const mousePreventDefault = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + }, []); + + const isIncluded = Options.includes(value); + return ( + { + onValueChange(value); + setInputValue(""); + }} + className={cn( + "rounded-md cursor-pointer px-2 py-1 transition-colors flex justify-between ", + className, + isIncluded && "opacity-50 cursor-default", + props.disabled && "opacity-50 cursor-not-allowed" + )} + onMouseDown={mousePreventDefault} + > + {children} + {isIncluded && } + + ); +}); + +MultiSelectorItem.displayName = "MultiSelectorItem"; + +export { + MultiSelector, + MultiSelectorTrigger, + MultiSelectorInput, + MultiSelectorContent, + MultiSelectorList, + MultiSelectorItem, + useMultiSelect, +}; diff --git a/packages/src/components/extension/fancy-multi-select/multi-select.tsx b/packages/src/components/extension/fancy-multi-select/multi-select.tsx index a576ebb..20661f1 100644 --- a/packages/src/components/extension/fancy-multi-select/multi-select.tsx +++ b/packages/src/components/extension/fancy-multi-select/multi-select.tsx @@ -10,7 +10,12 @@ import { import { cn } from "@/lib/utils"; import { Command as CommandPrimitive } from "cmdk"; import { X as RemoveIcon } from "lucide-react"; -import { KeyboardEvent, useCallback, useRef, useState } from "react"; +import React, { + KeyboardEvent, + useCallback, + useRef, + useState, +} from "react"; type Options = { value: string; @@ -143,4 +148,4 @@ export const MultiSelect = ({ ); -}; +}; \ No newline at end of file diff --git a/packages/src/components/extension/model.tsx b/packages/src/components/extension/model.tsx index a928748..5d92a58 100644 --- a/packages/src/components/extension/model.tsx +++ b/packages/src/components/extension/model.tsx @@ -13,6 +13,14 @@ import { } from "./carousel/carousel"; import { cn } from "@/lib/utils"; import { MultiSelect } from "./fancy-multi-select/multi-select"; +import { + MultiSelector, + MultiSelectorTrigger, + MultiSelectorContent, + MultiSelectorList, + MultiSelectorItem, + MultiSelectorInput, +} from "./fancy-multi-select/multi-select-api"; import { OtpStyledInput } from "./otp-input/otp-input"; import { Form, @@ -46,7 +54,6 @@ import { FileInput, } from "./file-uploader/file-uploader"; import { Paperclip } from "lucide-react"; -import * as AccordionPrimitive from "@radix-ui/react-accordion"; export type FilePreview = { file: File; @@ -340,7 +347,7 @@ export const Commander = () => { return (
{ ); }; -export const AccordionTest = () => { +export const MultiSelectorComp = () => { + const options = [ + { + value: "Next", + label: "Next", + }, + { + value: "React", + label: "React", + }, + { + value: "Tailwind", + label: "Tailwind", + }, + { + value: "Remix", + label: "Remix", + }, + { + value: "Astro", + label: "Astro", + }, + { + value: "Svelte", + label: "Svelte", + }, + { + value: "Solid", + label: "Solid", + }, + { + value: "Vue", + label: "Vue", + disabled: true, + }, + { + value: "Nuxt", + label: "Nuxt", + }, + { + value: "SvelteKit", + label: "SvelteKit", + }, + { + value: "Vite", + label: "Vite", + disabled: true, + }, + { + value: "Snowpack", + label: "Snowpack", + disabled: true, + }, + { + value: "Parcel", + label: "Parcel", + }, + { + value: "Webpack", + label: "Webpack", + }, + { + value: "Gatsby", + label: "Gatsby", + }, + ]; + const [value, onValueChange] = useState([]); + const notSelected = options.filter((item) => !value.includes(item.value)); return ( -
- - - Trigger 1 - Content 1 - - - Trigger 2 - - - - - Trigger 2.1 - - - - - - - - Trigger 3 - Content 3 - - -
+ + + + + + + {options.map((option, i) => ( + + {option.label} + + ))} + + + ); }; From 083e8f24185ee4c8cdbfa83c43fc13915cda4e36 Mon Sep 17 00:00:00 2001 From: BelkacemYerfa Date: Fri, 23 Feb 2024 16:51:49 +0100 Subject: [PATCH 08/10] change : the check icon --- .../extension/fancy-multi-select/multi-select-api.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/src/components/extension/fancy-multi-select/multi-select-api.tsx b/packages/src/components/extension/fancy-multi-select/multi-select-api.tsx index c8a1911..2072176 100644 --- a/packages/src/components/extension/fancy-multi-select/multi-select-api.tsx +++ b/packages/src/components/extension/fancy-multi-select/multi-select-api.tsx @@ -9,7 +9,7 @@ import { } from "@/components/ui/command"; import { cn } from "@/lib/utils"; import { Command as CommandPrimitive } from "cmdk"; -import { X as RemoveIcon, CheckCheck } from "lucide-react"; +import { X as RemoveIcon, Check } from "lucide-react"; import React, { KeyboardEvent, createContext, @@ -240,7 +240,7 @@ const MultiSelectorItem = forwardRef< onMouseDown={mousePreventDefault} > {children} - {isIncluded && } + {isIncluded && } ); }); From 06d27c4f670314668204bac2c5f56b0b1ae15c88 Mon Sep 17 00:00:00 2001 From: BelkacemYerfa Date: Fri, 23 Feb 2024 17:10:24 +0100 Subject: [PATCH 09/10] fix: --- packages/package-lock.json | 28 ++-- packages/package.json | 4 +- packages/src/app/page.tsx | 30 +---- .../fancy-multi-select/multi-select.tsx | 9 +- packages/src/components/extension/model.tsx | 123 ++++-------------- .../extension/tree-view/tree-view.tsx | 70 +--------- 6 files changed, 60 insertions(+), 204 deletions(-) diff --git a/packages/package-lock.json b/packages/package-lock.json index 246d1e2..a303fef 100644 --- a/packages/package-lock.json +++ b/packages/package-lock.json @@ -24,8 +24,8 @@ "clsx": "^2.1.0", "cmdk": "^0.2.0", "date-fns": "^3.3.1", - "embla-carousel": "^8.0.0", - "embla-carousel-react": "^8.0.0", + "embla-carousel": "^8.0.0-rc17", + "embla-carousel-react": "^8.0.0-rc17", "lucide-react": "^0.316.0", "next": "14.1.0", "next-themes": "^0.2.1", @@ -2475,28 +2475,28 @@ "dev": true }, "node_modules/embla-carousel": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.0.0.tgz", - "integrity": "sha512-ecixcyqS6oKD2nh5Nj5MObcgoSILWNI/GtBxkidn5ytFaCCmwVHo2SecksaQZHcARMMpIR2dWOlSIdA1LkZFUA==" + "version": "8.0.0-rc17", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.0.0-rc17.tgz", + "integrity": "sha512-evF49b88VOitvqFtlvhvKVSu96Y8A+QSFdhok87Bfm8R7OYuk95FT+o8+M1GQLi/EhGDUlT193HTVAR0Wt2neQ==" }, "node_modules/embla-carousel-react": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.0.0.tgz", - "integrity": "sha512-qT0dii8ZwoCtEIBE6ogjqU2+5IwnGfdt2teKjCzW88JRErflhlCpz8KjWnW8xoRZOP8g0clRtsMEFoAgS/elfA==", + "version": "8.0.0-rc17", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.0.0-rc17.tgz", + "integrity": "sha512-x4aFprwFB+PQO9EsHHZsrDxARb0uYNBYn9mr5oDFdBdPez4M8G1r5yidWbUcT9pNUc8AQXC9sGzlfauBfBxVOw==", "dependencies": { - "embla-carousel": "8.0.0", - "embla-carousel-reactive-utils": "8.0.0" + "embla-carousel": "8.0.0-rc17", + "embla-carousel-reactive-utils": "8.0.0-rc17" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.1 || ^18.0.0" } }, "node_modules/embla-carousel-reactive-utils": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.0.0.tgz", - "integrity": "sha512-JCw0CqCXI7tbHDRogBb9PoeMLyjEC1vpN0lDOzUjmlfVgtfF+ffLaOK8bVtXVUEbNs/3guGe3NSzA5J5aYzLzw==", + "version": "8.0.0-rc17", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.0.0-rc17.tgz", + "integrity": "sha512-eluEOK/u5HdjYaTLC4bUG3iTCnyX7RsYix3il0aH4ZECOKa5fS+pVK2vrM17Mgw6C5Hyjcr3r3lfJtGerVzVsQ==", "peerDependencies": { - "embla-carousel": "8.0.0" + "embla-carousel": "8.0.0-rc17" } }, "node_modules/emoji-regex": { diff --git a/packages/package.json b/packages/package.json index 42db997..845c285 100644 --- a/packages/package.json +++ b/packages/package.json @@ -25,8 +25,8 @@ "clsx": "^2.1.0", "cmdk": "^0.2.0", "date-fns": "^3.3.1", - "embla-carousel": "^8.0.0", - "embla-carousel-react": "^8.0.0", + "embla-carousel": "^8.0.0-rc17", + "embla-carousel-react": "^8.0.0-rc17", "lucide-react": "^0.316.0", "next": "14.1.0", "next-themes": "^0.2.1", diff --git a/packages/src/app/page.tsx b/packages/src/app/page.tsx index f70a4cc..ae1e6b9 100644 --- a/packages/src/app/page.tsx +++ b/packages/src/app/page.tsx @@ -1,36 +1,21 @@ /* import { MultiSelect } from "@/components/extension/fancy-multi-select/multi-select"; */ -import { BreadCrumb } from "@/components/extension/breadcrumb/bread-crumb"; -import { - ImageUpload, - Model, - OtpTest, - TreeViewTest, - Commander, - CommanderUsingUseState, - CarouselExample, - BreadCrumbTest, - FileUploaderTest, - TreeFileTest, - MultiSelectorComp, -} from "@/components/extension/model"; +import { TreeViewTest, TreeFileTest } from "@/components/extension/model"; import { ModeToggle } from "@/components/toggle-theme"; //provide the set of api to the component that allow to build the ui examples export default function Home() { return ( -
+
{/* */} - {/* - */} - - + {/* */} + {/* */} + {/* */} {/* */} {/* */} {/* */} - {} - {/*
+

Full Built in {"<"}TreeView /{">"}{" "} @@ -43,8 +28,7 @@ export default function Home() {

-
*/} - +
diff --git a/packages/src/components/extension/fancy-multi-select/multi-select.tsx b/packages/src/components/extension/fancy-multi-select/multi-select.tsx index 20661f1..a576ebb 100644 --- a/packages/src/components/extension/fancy-multi-select/multi-select.tsx +++ b/packages/src/components/extension/fancy-multi-select/multi-select.tsx @@ -10,12 +10,7 @@ import { import { cn } from "@/lib/utils"; import { Command as CommandPrimitive } from "cmdk"; import { X as RemoveIcon } from "lucide-react"; -import React, { - KeyboardEvent, - useCallback, - useRef, - useState, -} from "react"; +import { KeyboardEvent, useCallback, useRef, useState } from "react"; type Options = { value: string; @@ -148,4 +143,4 @@ export const MultiSelect = ({ ); -}; \ No newline at end of file +}; diff --git a/packages/src/components/extension/model.tsx b/packages/src/components/extension/model.tsx index 4618439..c802e1c 100644 --- a/packages/src/components/extension/model.tsx +++ b/packages/src/components/extension/model.tsx @@ -12,14 +12,6 @@ import { } from "./carousel/carousel"; import { cn } from "@/lib/utils"; import { MultiSelect } from "./fancy-multi-select/multi-select"; -import { - MultiSelector, - MultiSelectorTrigger, - MultiSelectorContent, - MultiSelectorList, - MultiSelectorItem, - MultiSelectorInput, -} from "./fancy-multi-select/multi-select-api"; import { OtpStyledInput } from "./otp-input/otp-input"; import { Form, @@ -53,6 +45,7 @@ import { FileInput, } from "./file-uploader/file-uploader"; import { Paperclip } from "lucide-react"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; export type FilePreview = { file: File; @@ -346,7 +339,7 @@ export const Commander = () => { return ( { ); }; -export const MultiSelectorComp = () => { - const options = [ - { - value: "Next", - label: "Next", - }, - { - value: "React", - label: "React", - }, - { - value: "Tailwind", - label: "Tailwind", - }, - { - value: "Remix", - label: "Remix", - }, - { - value: "Astro", - label: "Astro", - }, - { - value: "Svelte", - label: "Svelte", - }, - { - value: "Solid", - label: "Solid", - }, - { - value: "Vue", - label: "Vue", - disabled: true, - }, - { - value: "Nuxt", - label: "Nuxt", - }, - { - value: "SvelteKit", - label: "SvelteKit", - }, - { - value: "Vite", - label: "Vite", - disabled: true, - }, - { - value: "Snowpack", - label: "Snowpack", - disabled: true, - }, - { - value: "Parcel", - label: "Parcel", - }, - { - value: "Webpack", - label: "Webpack", - }, - { - value: "Gatsby", - label: "Gatsby", - }, - ]; - const [value, onValueChange] = useState([]); - const notSelected = options.filter((item) => !value.includes(item.value)); +export const AccordionTest = () => { return ( - - - - - - - {options.map((option, i) => ( - - {option.label} - - ))} - - - +
+ + + Trigger 1 + Content 1 + + + Trigger 2 + + + + + Trigger 2.1 + + + + + + + + Trigger 3 + Content 3 + + +
); }; diff --git a/packages/src/components/extension/tree-view/tree-view.tsx b/packages/src/components/extension/tree-view/tree-view.tsx index 1ccf2e5..8804ef7 100644 --- a/packages/src/components/extension/tree-view/tree-view.tsx +++ b/packages/src/components/extension/tree-view/tree-view.tsx @@ -204,63 +204,9 @@ export const TreeItem = forwardRef< )) ) : ( +
  • + ( - ( - { - className, - elements, - selectItem, - handleSelect, - expendedItems, - selectedId, - indicator, - ...props - }, - ref - ) => { - return ( -
      - {elements instanceof Array ? ( - elements.map((element) => ( -
    • - {element.children && element.children?.length > 0 ? ( - - - - ) : ( - - - {element?.name} - - )} -
    • - )) - ) : ( -
    • - {elements?.name} -
    • - )} -
    - ); - } -); +
  • + ) + } ) +}); TreeItem.displayName = "TreeItem"; @@ -384,4 +328,4 @@ export const File = forwardRef< ); }); -TreeItem.displayName = "TreeItem"; +TreeItem.displayName = "TreeItem"; \ No newline at end of file From 160cd46c4915d4fe2f58af86a87f453eee7dc1f2 Mon Sep 17 00:00:00 2001 From: BelkacemYerfa Date: Fri, 23 Feb 2024 17:50:29 +0100 Subject: [PATCH 10/10] add : the test component for the multi select api --- packages/src/components/extension/model.tsx | 115 +++++++++++++++----- 1 file changed, 89 insertions(+), 26 deletions(-) diff --git a/packages/src/components/extension/model.tsx b/packages/src/components/extension/model.tsx index c802e1c..f04a90e 100644 --- a/packages/src/components/extension/model.tsx +++ b/packages/src/components/extension/model.tsx @@ -46,6 +46,14 @@ import { } from "./file-uploader/file-uploader"; import { Paperclip } from "lucide-react"; import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorInput, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from "./fancy-multi-select/multi-select-api"; export type FilePreview = { file: File; @@ -706,32 +714,87 @@ export const TreeViewTest = () => { ); }; -export const AccordionTest = () => { +export const MultiSelectTest = () => { + const options = [ + { + value: "Next", + label: "Next", + }, + { + value: "React", + label: "React", + }, + { + value: "Tailwind", + label: "Tailwind", + }, + { + value: "Remix", + label: "Remix", + }, + { + value: "Astro", + label: "Astro", + }, + { + value: "Svelte", + label: "Svelte", + }, + { + value: "Solid", + label: "Solid", + }, + { + value: "Vue", + label: "Vue", + }, + { + value: "Nuxt", + label: "Nuxt", + }, + { + value: "SvelteKit", + label: "SvelteKit", + }, + { + value: "Vite", + label: "Vite", + disabled: true, + }, + { + value: "Snowpack", + label: "Snowpack", + disabled: true, + }, + { + value: "Parcel", + label: "Parcel", + }, + { + value: "Webpack", + label: "Webpack", + }, + { + value: "Gatsby", + label: "Gatsby", + disabled: true, + }, + ]; + const [value, setValue] = useState([]); return ( -
    - - - Trigger 1 - Content 1 - - - Trigger 2 - - - - - Trigger 2.1 - - - - - - - - Trigger 3 - Content 3 - - -
    + + + + + + + {options.map((option, i) => ( + + {option.label} + + ))} + + + ); };