feat: add initial implementation for fetching avatar data from API
- Created a new sync.ts file to handle API requests. - Defined interfaces for AvatarApiResponse, Data, and Avatar. - Implemented a main function to fetch avatar data from the specified endpoint.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { AutoTextSize } from "auto-text-size";
|
||||
import Image from "next/image";
|
||||
import type { characters } from "@/lib/db/schema";
|
||||
|
||||
// TODO: Change amber type
|
||||
export default function Avatar({
|
||||
char,
|
||||
scale = 1,
|
||||
}: {
|
||||
char: typeof characters.$inferInsert;
|
||||
scale?: number;
|
||||
}) {
|
||||
const star = (key: number) => (
|
||||
<svg
|
||||
key={key}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 576 512"
|
||||
style={{
|
||||
fill: "rgba(255,204,50)",
|
||||
filter: "drop-shadow(#e3721b 0px 0px 1px)",
|
||||
height: 20,
|
||||
width: 20,
|
||||
}}
|
||||
>
|
||||
<title>Star</title>
|
||||
<path d="M381.2 150.3l143.7 21.2c11.9 1.7 21.9 10.1 25.7 21.6 3.8 11.6.7 24.2-7.9 32.8L438.5 328.1l24.6 146.6c2 12-2.9 24.2-12.9 31.3-9.9 7.1-23 8-33.7 2.3l-128.4-68.5-128.3 68.5c-10.8 5.7-23.9 4.8-33.8-2.3-9.9-7.1-14.9-19.3-12.8-31.3l24.6-146.6L33.58 225.9c-8.61-8.6-11.67-21.2-7.89-32.8 3.77-11.5 13.74-19.9 25.73-21.6L195 150.3l64.4-132.33C264.7 6.954 275.9-.04 288.1-.04c12.3 0 23.5 6.994 28.8 18.01l64.3 132.33z"></path>
|
||||
</svg>
|
||||
);
|
||||
const stars = [];
|
||||
for (let i = 0; i < (char.stars || 0); i++) stars.push(star(i));
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: 128 * scale,
|
||||
height: 168 * scale,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "128px",
|
||||
background: "rgba(233, 229, 220)",
|
||||
borderRadius: ".5rem",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
transform: `scale(${scale})`,
|
||||
transformOrigin: "0 0",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={`/cdn/${char.image}`}
|
||||
style={{
|
||||
background: `rgba(${
|
||||
char.stars === 5
|
||||
? "200,124,36"
|
||||
: char.stars === 4
|
||||
? "148,112,187"
|
||||
: "100,100,100"
|
||||
}) linear-gradient(136deg,rgba(49,43,71,.5294117647058824),transparent)`,
|
||||
borderBottomRightRadius: "1.5rem",
|
||||
filter: char.stars ? "" : "grayscale(100%) opacity(10%) blur(2px)",
|
||||
}}
|
||||
alt={char.name}
|
||||
height={128}
|
||||
width={128}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
padding: "8px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignContent: "center",
|
||||
fontWeight: "bold",
|
||||
color: "rgba(74,83,102)",
|
||||
height: 40,
|
||||
}}
|
||||
>
|
||||
<AutoTextSize mode="oneline" maxFontSizePx={16}>
|
||||
{char.name}
|
||||
</AutoTextSize>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
top: "112px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{stars}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export function Blocker({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<div className="blocker backdrop-blur-xl bg-[#222A] flex justify-center items-center absolute top-[-5px] left-[-5px] bottom-[-5px] right-[-5px] rounded-sm z-100 border-t-gray-700 border-l-gray-600 border-1">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+75
-90
@@ -1,28 +1,28 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import {
|
||||
expandAllFeature,
|
||||
hotkeysCoreFeature,
|
||||
searchFeature,
|
||||
selectionFeature,
|
||||
syncDataLoaderFeature,
|
||||
TreeState,
|
||||
} from "@headless-tree/core"
|
||||
import { useTree } from "@headless-tree/react"
|
||||
type TreeState,
|
||||
} from "@headless-tree/core";
|
||||
import { useTree } from "@headless-tree/react";
|
||||
import {
|
||||
CircleXIcon,
|
||||
FilterIcon,
|
||||
FolderIcon,
|
||||
FolderOpenIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Tree, TreeItem, TreeItemLabel } from "@/components/tree"
|
||||
} from "lucide-react";
|
||||
import type React from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Tree, TreeItem, TreeItemLabel } from "@/components/tree";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
interface Item {
|
||||
name: string
|
||||
children?: string[]
|
||||
name: string;
|
||||
children?: string[];
|
||||
}
|
||||
|
||||
const items: Record<string, Item> = {
|
||||
@@ -53,16 +53,16 @@ const items: Record<string, Item> = {
|
||||
operations: { name: "Operations", children: ["hr", "finance"] },
|
||||
hr: { name: "HR" },
|
||||
finance: { name: "Finance" },
|
||||
}
|
||||
};
|
||||
|
||||
const indent = 20
|
||||
const indent = 20;
|
||||
|
||||
export default function Component() {
|
||||
// Store the initial expanded items to reset when search is cleared
|
||||
const initialExpandedItems = ["engineering", "frontend", "design-system"]
|
||||
const [state, setState] = useState<Partial<TreeState<Item>>>({})
|
||||
const [searchValue, setSearchValue] = useState("")
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const initialExpandedItems = ["engineering", "frontend", "design-system"];
|
||||
const [state, setState] = useState<Partial<TreeState<Item>>>({});
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const tree = useTree<Item>({
|
||||
state,
|
||||
@@ -85,115 +85,115 @@ export default function Component() {
|
||||
searchFeature,
|
||||
expandAllFeature,
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
// Handle clearing the search
|
||||
const handleClearSearch = () => {
|
||||
setSearchValue("")
|
||||
setSearchValue("");
|
||||
|
||||
// Manually trigger the tree's search onChange with an empty value
|
||||
// to ensure item.isMatchingSearch() is correctly updated.
|
||||
const searchProps = tree.getSearchInputElementProps()
|
||||
const searchProps = tree.getSearchInputElementProps();
|
||||
if (searchProps.onChange) {
|
||||
const syntheticEvent = {
|
||||
target: { value: "" },
|
||||
} as React.ChangeEvent<HTMLInputElement> // Cast to the expected event type
|
||||
searchProps.onChange(syntheticEvent)
|
||||
} as React.ChangeEvent<HTMLInputElement>; // Cast to the expected event type
|
||||
searchProps.onChange(syntheticEvent);
|
||||
}
|
||||
|
||||
// Reset tree state to initial expanded items
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
expandedItems: initialExpandedItems,
|
||||
}))
|
||||
}));
|
||||
|
||||
// Clear custom filtered items
|
||||
setFilteredItems([])
|
||||
setFilteredItems([]);
|
||||
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus()
|
||||
inputRef.current.focus();
|
||||
// Also clear the internal search input
|
||||
inputRef.current.value = ""
|
||||
inputRef.current.value = "";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Keep track of filtered items separately from the tree's internal search state
|
||||
const [filteredItems, setFilteredItems] = useState<string[]>([])
|
||||
const [filteredItems, setFilteredItems] = useState<string[]>([]);
|
||||
|
||||
// This function determines if an item should be visible based on our custom filtering
|
||||
const shouldShowItem = (itemId: string) => {
|
||||
if (!searchValue || searchValue.length === 0) return true
|
||||
return filteredItems.includes(itemId)
|
||||
}
|
||||
if (!searchValue || searchValue.length === 0) return true;
|
||||
return filteredItems.includes(itemId);
|
||||
};
|
||||
|
||||
// Update filtered items when search value changes
|
||||
useEffect(() => {
|
||||
if (!searchValue || searchValue.length === 0) {
|
||||
setFilteredItems([])
|
||||
return
|
||||
setFilteredItems([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all items
|
||||
const allItems = tree.getItems()
|
||||
const allItems = tree.getItems();
|
||||
|
||||
// First, find direct matches
|
||||
const directMatches = allItems
|
||||
.filter((item) => {
|
||||
const name = item.getItemName().toLowerCase()
|
||||
return name.includes(searchValue.toLowerCase())
|
||||
const name = item.getItemName().toLowerCase();
|
||||
return name.includes(searchValue.toLowerCase());
|
||||
})
|
||||
.map((item) => item.getId())
|
||||
.map((item) => item.getId());
|
||||
|
||||
// Then, find all parent IDs of matching items
|
||||
const parentIds = new Set<string>()
|
||||
const parentIds = new Set<string>();
|
||||
directMatches.forEach((matchId) => {
|
||||
let item = tree.getItems().find((i) => i.getId() === matchId)
|
||||
while (item?.getParent && item.getParent()) {
|
||||
const parent = item.getParent()
|
||||
let item = tree.getItems().find((i) => i.getId() === matchId);
|
||||
while (item?.getParent?.()) {
|
||||
const parent = item.getParent();
|
||||
if (parent) {
|
||||
parentIds.add(parent.getId())
|
||||
item = parent
|
||||
parentIds.add(parent.getId());
|
||||
item = parent;
|
||||
} else {
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Find all children of matching items
|
||||
const childrenIds = new Set<string>()
|
||||
const childrenIds = new Set<string>();
|
||||
directMatches.forEach((matchId) => {
|
||||
const item = tree.getItems().find((i) => i.getId() === matchId)
|
||||
if (item && item.isFolder()) {
|
||||
const item = tree.getItems().find((i) => i.getId() === matchId);
|
||||
if (item?.isFolder()) {
|
||||
// Get all descendants recursively
|
||||
const getDescendants = (itemId: string) => {
|
||||
const children = items[itemId]?.children || []
|
||||
const children = items[itemId]?.children || [];
|
||||
children.forEach((childId) => {
|
||||
childrenIds.add(childId)
|
||||
childrenIds.add(childId);
|
||||
if (items[childId]?.children?.length) {
|
||||
getDescendants(childId)
|
||||
getDescendants(childId);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
getDescendants(item.getId())
|
||||
getDescendants(item.getId());
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Combine direct matches, parents, and children
|
||||
setFilteredItems([
|
||||
...directMatches,
|
||||
...Array.from(parentIds),
|
||||
...Array.from(childrenIds),
|
||||
])
|
||||
]);
|
||||
|
||||
// Keep all folders expanded during search to ensure all matches are visible
|
||||
// Store current expanded state first
|
||||
const currentExpandedItems = tree.getState().expandedItems || []
|
||||
const currentExpandedItems = tree.getState().expandedItems || [];
|
||||
|
||||
// Get all folder IDs that need to be expanded to show matches
|
||||
const folderIdsToExpand = allItems
|
||||
.filter((item) => item.isFolder())
|
||||
.map((item) => item.getId())
|
||||
.map((item) => item.getId());
|
||||
|
||||
// Update expanded items in the tree state
|
||||
setState((prevState) => ({
|
||||
@@ -201,8 +201,8 @@ export default function Component() {
|
||||
expandedItems: [
|
||||
...new Set([...currentExpandedItems, ...folderIdsToExpand]),
|
||||
],
|
||||
}))
|
||||
}, [searchValue, tree])
|
||||
}));
|
||||
}, [searchValue, tree]);
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col gap-2 *:nth-2:grow">
|
||||
@@ -212,40 +212,40 @@ export default function Component() {
|
||||
className="peer ps-9"
|
||||
value={searchValue}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value
|
||||
setSearchValue(value)
|
||||
const value = e.target.value;
|
||||
setSearchValue(value);
|
||||
|
||||
// Apply the search to the tree's internal state as well
|
||||
const searchProps = tree.getSearchInputElementProps()
|
||||
const searchProps = tree.getSearchInputElementProps();
|
||||
if (searchProps.onChange) {
|
||||
searchProps.onChange(e)
|
||||
searchProps.onChange(e);
|
||||
}
|
||||
|
||||
if (value.length > 0) {
|
||||
// If input has at least one character, expand all items
|
||||
tree.expandAll()
|
||||
tree.expandAll();
|
||||
} else {
|
||||
// If input is cleared, reset to initial expanded state
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
expandedItems: initialExpandedItems,
|
||||
}))
|
||||
setFilteredItems([])
|
||||
}));
|
||||
setFilteredItems([]);
|
||||
}
|
||||
}}
|
||||
// Prevent the internal search from being cleared on blur
|
||||
onBlur={(e) => {
|
||||
// Prevent default blur behavior
|
||||
e.preventDefault()
|
||||
e.preventDefault();
|
||||
|
||||
// Re-apply the search to ensure it stays active
|
||||
if (searchValue && searchValue.length > 0) {
|
||||
const searchProps = tree.getSearchInputElementProps()
|
||||
const searchProps = tree.getSearchInputElementProps();
|
||||
if (searchProps.onChange) {
|
||||
const syntheticEvent = {
|
||||
target: { value: searchValue },
|
||||
} as React.ChangeEvent<HTMLInputElement>
|
||||
searchProps.onChange(syntheticEvent)
|
||||
} as React.ChangeEvent<HTMLInputElement>;
|
||||
searchProps.onChange(syntheticEvent);
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -260,6 +260,7 @@ export default function Component() {
|
||||
className="text-muted-foreground/80 hover:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 absolute inset-y-0 end-0 flex h-full w-9 items-center justify-center rounded-e-md transition-[color,box-shadow] outline-none focus:z-10 focus-visible:ring-[3px] disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
aria-label="Clear search"
|
||||
onClick={handleClearSearch}
|
||||
type="button"
|
||||
>
|
||||
<CircleXIcon className="size-4" aria-hidden="true" />
|
||||
</button>
|
||||
@@ -273,7 +274,7 @@ export default function Component() {
|
||||
</p>
|
||||
) : (
|
||||
tree.getItems().map((item) => {
|
||||
const isVisible = shouldShowItem(item.getId())
|
||||
const isVisible = shouldShowItem(item.getId());
|
||||
|
||||
return (
|
||||
<TreeItem
|
||||
@@ -294,26 +295,10 @@ export default function Component() {
|
||||
</span>
|
||||
</TreeItemLabel>
|
||||
</TreeItem>
|
||||
)
|
||||
);
|
||||
})
|
||||
)}
|
||||
</Tree>
|
||||
|
||||
<p
|
||||
aria-live="polite"
|
||||
role="region"
|
||||
className="text-muted-foreground mt-2 text-xs"
|
||||
>
|
||||
Tree with filtering ∙{" "}
|
||||
<a
|
||||
href="https://headless-tree.lukasbach.com"
|
||||
className="hover:text-foreground underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
API
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
+44
-44
@@ -1,48 +1,48 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import { ItemInstance } from "@headless-tree/core"
|
||||
import { ChevronDownIcon } from "lucide-react"
|
||||
import { Slot } from "radix-ui"
|
||||
import type { ItemInstance } from "@headless-tree/core";
|
||||
import { ChevronDownIcon } from "lucide-react";
|
||||
import { Slot } from "radix-ui";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface TreeContextValue<T = any> {
|
||||
indent: number
|
||||
currentItem?: ItemInstance<T>
|
||||
tree?: any
|
||||
indent: number;
|
||||
currentItem?: ItemInstance<T>;
|
||||
tree?: any;
|
||||
}
|
||||
|
||||
const TreeContext = React.createContext<TreeContextValue>({
|
||||
indent: 20,
|
||||
currentItem: undefined,
|
||||
tree: undefined,
|
||||
})
|
||||
});
|
||||
|
||||
function useTreeContext<T = any>() {
|
||||
return React.useContext(TreeContext) as TreeContextValue<T>
|
||||
return React.useContext(TreeContext) as TreeContextValue<T>;
|
||||
}
|
||||
|
||||
interface TreeProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
indent?: number
|
||||
tree?: any
|
||||
indent?: number;
|
||||
tree?: any;
|
||||
}
|
||||
|
||||
function Tree({ indent = 20, tree, className, ...props }: TreeProps) {
|
||||
const containerProps =
|
||||
tree && typeof tree.getContainerProps === "function"
|
||||
? tree.getContainerProps()
|
||||
: {}
|
||||
const mergedProps = { ...props, ...containerProps }
|
||||
: {};
|
||||
const mergedProps = { ...props, ...containerProps };
|
||||
|
||||
// Extract style from mergedProps to merge with our custom styles
|
||||
const { style: propStyle, ...otherProps } = mergedProps
|
||||
const { style: propStyle, ...otherProps } = mergedProps;
|
||||
|
||||
// Merge styles
|
||||
const mergedStyle = {
|
||||
...propStyle,
|
||||
"--tree-indent": `${indent}px`,
|
||||
} as React.CSSProperties
|
||||
} as React.CSSProperties;
|
||||
|
||||
return (
|
||||
<TreeContext.Provider value={{ indent, tree }}>
|
||||
@@ -53,14 +53,14 @@ function Tree({ indent = 20, tree, className, ...props }: TreeProps) {
|
||||
{...otherProps}
|
||||
/>
|
||||
</TreeContext.Provider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
interface TreeItemProps<T = any>
|
||||
extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
item: ItemInstance<T>
|
||||
indent?: number
|
||||
asChild?: boolean
|
||||
item: ItemInstance<T>;
|
||||
indent?: number;
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
function TreeItem<T = any>({
|
||||
@@ -70,21 +70,21 @@ function TreeItem<T = any>({
|
||||
children,
|
||||
...props
|
||||
}: Omit<TreeItemProps<T>, "indent">) {
|
||||
const { indent } = useTreeContext<T>()
|
||||
const { indent } = useTreeContext<T>();
|
||||
|
||||
const itemProps = typeof item.getProps === "function" ? item.getProps() : {}
|
||||
const mergedProps = { ...props, ...itemProps }
|
||||
const itemProps = typeof item.getProps === "function" ? item.getProps() : {};
|
||||
const mergedProps = { ...props, ...itemProps };
|
||||
|
||||
// Extract style from mergedProps to merge with our custom styles
|
||||
const { style: propStyle, ...otherProps } = mergedProps
|
||||
const { style: propStyle, ...otherProps } = mergedProps;
|
||||
|
||||
// Merge styles
|
||||
const mergedStyle = {
|
||||
...propStyle,
|
||||
"--tree-padding": `${item.getItemMeta().level * indent}px`,
|
||||
} as React.CSSProperties
|
||||
} as React.CSSProperties;
|
||||
|
||||
const Comp = asChild ? Slot.Root : "button"
|
||||
const Comp = asChild ? Slot.Root : "button";
|
||||
|
||||
return (
|
||||
<TreeContext.Provider value={{ indent, currentItem: item }}>
|
||||
@@ -93,7 +93,7 @@ function TreeItem<T = any>({
|
||||
style={mergedStyle}
|
||||
className={cn(
|
||||
"z-10 ps-(--tree-padding) outline-hidden select-none not-last:pb-0.5 focus:z-20 data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
data-focus={
|
||||
typeof item.isFocused === "function"
|
||||
@@ -126,12 +126,12 @@ function TreeItem<T = any>({
|
||||
{children}
|
||||
</Comp>
|
||||
</TreeContext.Provider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
interface TreeItemLabelProps<T = any>
|
||||
extends React.HTMLAttributes<HTMLSpanElement> {
|
||||
item?: ItemInstance<T>
|
||||
item?: ItemInstance<T>;
|
||||
}
|
||||
|
||||
function TreeItemLabel<T = any>({
|
||||
@@ -140,12 +140,12 @@ function TreeItemLabel<T = any>({
|
||||
className,
|
||||
...props
|
||||
}: TreeItemLabelProps<T>) {
|
||||
const { currentItem } = useTreeContext<T>()
|
||||
const item = propItem || currentItem
|
||||
const { currentItem } = useTreeContext<T>();
|
||||
const item = propItem || currentItem;
|
||||
|
||||
if (!item) {
|
||||
console.warn("TreeItemLabel: No item provided via props or context")
|
||||
return null
|
||||
console.warn("TreeItemLabel: No item provided via props or context");
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -153,7 +153,7 @@ function TreeItemLabel<T = any>({
|
||||
data-slot="tree-item-label"
|
||||
className={cn(
|
||||
"in-focus-visible:ring-ring/50 bg-background hover:bg-accent in-data-[selected=true]:bg-accent in-data-[selected=true]:text-accent-foreground in-data-[drag-target=true]:bg-accent flex items-center gap-1 rounded-sm px-2 py-1.5 text-sm transition-colors not-in-data-[folder=true]:ps-7 in-focus-visible:ring-[3px] in-data-[search-match=true]:bg-blue-400/20! [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -163,33 +163,33 @@ function TreeItemLabel<T = any>({
|
||||
{children ||
|
||||
(typeof item.getItemName === "function" ? item.getItemName() : null)}
|
||||
</span>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function TreeDragLine({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const { tree } = useTreeContext()
|
||||
const { tree } = useTreeContext();
|
||||
|
||||
if (!tree || typeof tree.getDragLineStyle !== "function") {
|
||||
console.warn(
|
||||
"TreeDragLine: No tree provided via context or tree does not have getDragLineStyle method"
|
||||
)
|
||||
return null
|
||||
"TreeDragLine: No tree provided via context or tree does not have getDragLineStyle method",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const dragLine = tree.getDragLineStyle()
|
||||
const dragLine = tree.getDragLineStyle();
|
||||
return (
|
||||
<div
|
||||
style={dragLine}
|
||||
className={cn(
|
||||
"bg-primary before:bg-background before:border-primary absolute z-30 -mt-px h-0.5 w-[unset] before:absolute before:-top-[3px] before:left-0 before:size-2 before:rounded-full before:border-2",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Tree, TreeItem, TreeItemLabel, TreeDragLine }
|
||||
export { Tree, TreeItem, TreeItemLabel, TreeDragLine };
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||
import type * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function ScrollArea({
|
||||
className,
|
||||
@@ -25,7 +25,7 @@ function ScrollArea({
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ScrollBar({
|
||||
@@ -43,16 +43,16 @@ function ScrollBar({
|
||||
"h-full w-2.5 border-l border-l-transparent",
|
||||
orientation === "horizontal" &&
|
||||
"h-2.5 flex-col border-t border-t-transparent",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb
|
||||
data-slot="scroll-area-thumb"
|
||||
className="bg-border relative flex-1 rounded-full"
|
||||
className="bg-white opacity-50 relative flex-1 rounded-full"
|
||||
/>
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { ScrollArea, ScrollBar }
|
||||
export { ScrollArea, ScrollBar };
|
||||
|
||||
Reference in New Issue
Block a user