@stratakit/structures
Version:
Medium-sized component structures for the Strata design system
489 lines (488 loc) • 15.3 kB
JavaScript
import { c as _c } from "react-compiler-runtime";
import { jsx, jsxs } from "react/jsx-runtime";
import * as React from "react";
import { CompositeItem } from "@ariakit/react/composite";
import { Role } from "@ariakit/react/role";
import { Toolbar, ToolbarItem } from "@ariakit/react/toolbar";
import { IconButton } from "@stratakit/bricks";
import { GhostAligner, IconButtonPresentation } from "@stratakit/bricks/secret-internals";
import { Icon } from "@stratakit/foundations";
import { forwardRef, useEventHandlers } from "@stratakit/foundations/secret-internals";
import cx from "classnames";
import { ChevronDown, MoreHorizontal, StatusIcon } from "./~utils.icons.js";
import * as ListItem from "./~utils.ListItem.js";
import * as DropdownMenu from "./DropdownMenu.js";
const TreeItemErrorContext = React.createContext(void 0);
const TreeItemInlineActionsContext = React.createContext(void 0);
const TreeItemActionsContext = React.createContext(void 0);
const TreeItemDecorationsContext = React.createContext(void 0);
const TreeItemIconContext = React.createContext(void 0);
const TreeItemDecorationIdContext = React.createContext(void 0);
const TreeItemLabelContext = React.createContext(void 0);
const TreeItemLabelIdContext = React.createContext(void 0);
const TreeItemDescriptionContext = React.createContext(void 0);
const TreeItemDescriptionIdContext = React.createContext(void 0);
const TreeItem = React.memo(forwardRef((props, forwardedRef) => {
const {
selected,
onSelectedChange,
expanded,
onExpandedChange,
icon: _icon,
unstable_decorations: _unstable_decorations,
label: _label,
description: _description,
inlineActions: _inlineActions,
actions: _actions,
error: _error,
onClick: onClickProp,
onKeyDown: onKeyDownProp,
...rest
} = props;
const onExpanderClick = useEventHandlers(() => {
if (expanded === void 0) return;
onExpandedChange?.(!expanded);
});
const handleClick = (event) => {
if (selected !== void 0) {
event.stopPropagation();
onSelectedChange?.(!selected);
return;
}
if (expanded === void 0) return;
event.stopPropagation();
onExpandedChange?.(!expanded);
};
const handleKeyDown = (event_0) => {
if (event_0.altKey || event_0.ctrlKey || event_0.metaKey || event_0.shiftKey) {
return;
}
if (expanded === void 0) return;
if (event_0.key === "ArrowRight" || event_0.key === "ArrowLeft") {
event_0.preventDefault();
onExpandedChange?.(event_0.key === "ArrowRight");
}
};
return /* @__PURE__ */ jsx(TreeItemRootProvider, {
...props,
children: /* @__PURE__ */ jsx(TreeItemRoot, {
...rest,
expanded,
selected,
onClick: useEventHandlers(onClickProp, handleClick),
onKeyDown: useEventHandlers(onKeyDownProp, handleKeyDown),
ref: forwardedRef,
children: React.useMemo(() => /* @__PURE__ */ jsx(TreeItemNode, {
onExpanderClick,
expanded,
selected
}), [onExpanderClick, expanded, selected])
})
});
}));
function TreeItemRootProvider(props) {
const $ = _c(12);
const {
inlineActions,
actions,
label,
description,
icon: iconProp,
unstable_decorations: decorations,
error
} = props;
const labelId = React.useId();
const descriptionId = React.useId();
const decorationId = React.useId();
let t0;
if ($[0] !== actions || $[1] !== decorationId || $[2] !== decorations || $[3] !== description || $[4] !== descriptionId || $[5] !== error || $[6] !== iconProp || $[7] !== inlineActions || $[8] !== label || $[9] !== labelId || $[10] !== props.children) {
const icon = error ? jsx(StatusIcon, {
tone: "attention"
}) : iconProp;
const hasDecoration = icon || decorations;
t0 = jsx(TreeItemErrorContext.Provider, {
value: error,
children: jsx(TreeItemInlineActionsContext.Provider, {
value: inlineActions,
children: jsx(TreeItemActionsContext.Provider, {
value: actions,
children: jsx(TreeItemDisplayActionsMenuContext.Provider, {
value: actions ? actions.length > 0 : false,
children: jsx(TreeItemDecorationIdContext.Provider, {
value: hasDecoration ? decorationId : void 0,
children: jsx(TreeItemDecorationsContext.Provider, {
value: decorations,
children: jsx(TreeItemIconContext.Provider, {
value: icon,
children: jsx(TreeItemLabelIdContext.Provider, {
value: labelId,
children: jsx(TreeItemLabelContext.Provider, {
value: label,
children: jsx(TreeItemDescriptionContext.Provider, {
value: description,
children: jsx(TreeItemDescriptionIdContext.Provider, {
value: description ? descriptionId : void 0,
children: props.children
})
})
})
})
})
})
})
})
})
})
});
$[0] = actions;
$[1] = decorationId;
$[2] = decorations;
$[3] = description;
$[4] = descriptionId;
$[5] = error;
$[6] = iconProp;
$[7] = inlineActions;
$[8] = label;
$[9] = labelId;
$[10] = props.children;
$[11] = t0;
} else {
t0 = $[11];
}
return t0;
}
const TreeItemRoot = React.memo(forwardRef((props, forwardedRef) => {
const {
style: styleProp,
"aria-level": level,
selected,
expanded,
...rest
} = props;
const labelId = React.useContext(TreeItemLabelIdContext);
const decorationId = React.useContext(TreeItemDecorationIdContext);
const descriptionId = React.useContext(TreeItemDescriptionIdContext);
const error = React.useContext(TreeItemErrorContext);
const errorId = typeof error === "string" ? error : void 0;
const describedBy = React.useMemo(() => {
const ids = [];
if (descriptionId) ids.push(descriptionId);
if (decorationId) ids.push(decorationId);
if (errorId) ids.push(errorId);
return ids.length > 0 ? ids.join(" ") : void 0;
}, [decorationId, descriptionId, errorId]);
const style = React.useMemo(() => ({
...styleProp,
"--\u{1F95D}TreeItem-level": level
}), [styleProp, level]);
return /* @__PURE__ */ jsx(CompositeItem, {
render: /* @__PURE__ */ jsx(Role, {
...rest,
role: "treeitem",
"aria-expanded": expanded,
"aria-selected": selected,
"aria-labelledby": labelId,
"aria-describedby": describedBy,
"aria-level": level,
className: cx("\u{1F95D}TreeItem", props.className),
style,
ref: forwardedRef
}),
children: props.children
});
}));
function useRenderActions() {
const $ = _c(5);
const ref = React.useRef(null);
const [renderActions, setRenderActions] = React.useState(false);
let t0;
let t1;
if ($[0] !== renderActions) {
t0 = () => {
const el = ref.current;
if (!el || renderActions) {
return;
}
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (!entry.isIntersecting) {
continue;
}
React.startTransition(() => {
setRenderActions(true);
});
}
});
observer.observe(el);
return () => {
observer.disconnect();
};
};
t1 = [renderActions];
$[0] = renderActions;
$[1] = t0;
$[2] = t1;
} else {
t0 = $[1];
t1 = $[2];
}
React.useEffect(t0, t1);
let t2;
if ($[3] !== renderActions) {
t2 = [ref, renderActions];
$[3] = renderActions;
$[4] = t2;
} else {
t2 = $[4];
}
return t2;
}
const TreeItemNode = React.memo((props) => {
const $ = _c(7);
const {
expanded,
selected,
onExpanderClick
} = props;
const error = React.useContext(TreeItemErrorContext);
const [ref, renderActions] = useRenderActions();
const t0 = error ? true : void 0;
let t1;
if ($[0] !== expanded || $[1] !== onExpanderClick || $[2] !== ref || $[3] !== renderActions || $[4] !== selected || $[5] !== t0) {
t1 = jsxs(ListItem.Root, {
"data-_sk-expanded": expanded,
"data-_sk-selected": selected,
"data-_sk-error": t0,
className: "\u{1F95D}TreeItemNode",
role: void 0,
ref,
children: [jsx(TreeItemDecorations, {
onExpanderClick
}), jsx(TreeItemContent, {}), jsx(TreeItemDescription, {}), renderActions && jsx(TreeItemActions, {})]
});
$[0] = expanded;
$[1] = onExpanderClick;
$[2] = ref;
$[3] = renderActions;
$[4] = selected;
$[5] = t0;
$[6] = t1;
} else {
t1 = $[6];
}
return t1;
});
const TreeItemDecorations = React.memo((props) => {
return /* @__PURE__ */ jsxs(ListItem.Decoration, {
children: [/* @__PURE__ */ jsx(TreeItemExpander, {
onClick: props.onExpanderClick
}), /* @__PURE__ */ jsx(TreeItemDecoration, {})]
});
});
function TreeItemDecoration() {
const $ = _c(4);
const decorationId = React.useContext(TreeItemDecorationIdContext);
const decorations = React.useContext(TreeItemDecorationsContext);
const icon = React.useContext(TreeItemIconContext);
let t0;
if ($[0] !== decorationId || $[1] !== decorations || $[2] !== icon) {
t0 = icon || decorations ? jsx(Role, {
className: "\u{1F95D}TreeItemDecoration",
id: decorationId,
render: React.isValidElement(icon) ? icon : typeof icon === "string" ? jsx(Icon, {
href: icon
}) : void 0,
children: !icon ? decorations : null
}) : null;
$[0] = decorationId;
$[1] = decorations;
$[2] = icon;
$[3] = t0;
} else {
t0 = $[3];
}
return t0;
}
const TreeItemContent = React.memo(() => {
const $ = _c(3);
const labelId = React.useContext(TreeItemLabelIdContext);
const label = React.useContext(TreeItemLabelContext);
let t0;
if ($[0] !== label || $[1] !== labelId) {
t0 = jsx(ListItem.Content, {
id: labelId,
className: "\u{1F95D}TreeItemContent",
children: label
});
$[0] = label;
$[1] = labelId;
$[2] = t0;
} else {
t0 = $[2];
}
return t0;
});
const TreeItemDescription = React.memo(() => {
const $ = _c(3);
const description = React.useContext(TreeItemDescriptionContext);
const descriptionId = React.useContext(TreeItemDescriptionIdContext);
let t0;
if ($[0] !== description || $[1] !== descriptionId) {
t0 = description ? jsx(ListItem.Content, {
id: descriptionId,
className: "\u{1F95D}TreeItemDescription",
children: description
}) : void 0;
$[0] = description;
$[1] = descriptionId;
$[2] = t0;
} else {
t0 = $[2];
}
return t0;
});
const TreeItemActions = React.memo(forwardRef((props, forwardedRef) => {
return /* @__PURE__ */ jsxs(ListItem.Decoration, {
...props,
onClick: useEventHandlers(props.onClick, (e) => e.stopPropagation()),
onKeyDown: useEventHandlers(props.onKeyDown, (e_0) => e_0.stopPropagation()),
className: cx("\u{1F95D}TreeItemActionsContainer", props.className),
ref: forwardedRef,
render: /* @__PURE__ */ jsx(Toolbar, {
focusLoop: false
}),
children: [/* @__PURE__ */ jsx(TreeItemInlineActionsRenderer, {}), /* @__PURE__ */ jsx(TreeItemActionMenu, {})]
});
}));
function TreeItemInlineActionsRenderer() {
const $ = _c(2);
const actions = React.useContext(TreeItemInlineActionsContext) ?? [];
let t0;
if ($[0] !== actions) {
t0 = actions.slice(0, 2);
$[0] = actions;
$[1] = t0;
} else {
t0 = $[1];
}
const actionsToDisplay = t0;
return actionsToDisplay;
}
const arrowKeys = ["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight"];
const TreeItemActionsMenuContext = React.createContext(false);
const TreeItemDisplayActionsMenuContext = React.createContext(false);
const TreeItemActionMenu = React.memo(forwardRef((props, forwardedRef) => {
const [open, _setOpen] = React.useState(false);
const isArrowKeyPressed = React.useRef(false);
const displayMenu = React.useContext(TreeItemDisplayActionsMenuContext);
const setOpen = React.useCallback((value) => {
if (value && !isArrowKeyPressed.current) {
_setOpen(true);
} else {
_setOpen(false);
}
}, []);
if (!displayMenu) return null;
return /* @__PURE__ */ jsxs(DropdownMenu.Provider, {
open,
setOpen,
placement: "right-start",
children: [/* @__PURE__ */ jsx(DropdownMenu.Button, {
...props,
onKeyDown: (e) => {
if (arrowKeys.includes(e.key)) {
isArrowKeyPressed.current = true;
}
queueMicrotask(() => {
isArrowKeyPressed.current = false;
});
},
render: /* @__PURE__ */ jsx(TreeItemInlineAction, {
label: "More",
icon: /* @__PURE__ */ jsx(MoreHorizontal, {})
}),
ref: forwardedRef
}), /* @__PURE__ */ jsx(TreeItemMenuActionsContent, {})]
});
}));
function TreeItemMenuActionsContent() {
return /* @__PURE__ */ jsx(TreeItemActionsMenuContext.Provider, {
value: true,
children: /* @__PURE__ */ jsx(DropdownMenu.Content, {
children: /* @__PURE__ */ jsx(TreeItemActionsRenderer, {})
})
});
}
function TreeItemActionsRenderer() {
const actions = React.useContext(TreeItemActionsContext) ?? [];
return actions;
}
const TreeItemAction = React.memo(forwardRef((props, forwardedRef) => {
const {
label,
icon,
dot,
...rest
} = props;
const actionsMenuContext = React.useContext(TreeItemActionsMenuContext);
if (actionsMenuContext) {
return /* @__PURE__ */ jsx(DropdownMenu.Item, {
...rest,
label,
icon,
unstable_dot: dot,
ref: forwardedRef
});
}
return /* @__PURE__ */ jsx(TreeItemInlineAction, {
...props,
ref: forwardedRef
});
}));
const TreeItemInlineAction = React.memo(forwardRef((props, forwardedRef) => {
const error = React.useContext(TreeItemErrorContext);
const generatedId = React.useId();
const {
id = generatedId,
visible = error ? true : void 0,
// visible by default during error state
label,
icon,
dot,
...rest
} = props;
return /* @__PURE__ */ jsx(IconButton, {
id,
label,
icon,
inert: visible === false ? "true" : void 0,
...rest,
render: /* @__PURE__ */ jsx(ToolbarItem, {
render: props.render
}),
dot,
variant: "ghost",
className: cx("\u{1F95D}TreeItemAction", props.className),
"data-_sk-visible": visible,
ref: forwardedRef
});
}));
const TreeItemExpander = forwardRef((props, forwardedRef) => {
const descriptionId = React.useContext(TreeItemDescriptionIdContext);
return /* @__PURE__ */ jsx(GhostAligner, {
align: descriptionId ? "block" : void 0,
children: /* @__PURE__ */ jsx(IconButtonPresentation, {
"aria-hidden": "true",
...props,
onClick: useEventHandlers(props.onClick, (e) => e.stopPropagation()),
className: cx("\u{1F95D}TreeItemExpander", props.className),
variant: "ghost",
ref: forwardedRef,
children: /* @__PURE__ */ jsx(ChevronDown, {})
})
});
});
export {
TreeItemAction as Action,
TreeItem as Root
};