import type { Tree, TreeGroupNode, TreeState, } from "@charcuterie/logic" import { selectChildNodes } from "@charcuterie/logic" import type { ReactNode } from "react" import { Button } from "../Button/Button.tsx" import { Card } from "../Card/Card.tsx" import { IconButton } from "../IconButton/IconButton.tsx" import { toClassName } from "../toClassName.ts" import type { QueryBuilderLabels } from "./QueryBuilder.tsx" import { QueryBuilderCombinator } from "./QueryBuilderCombinator.tsx" import { QueryBuilderRow } from "./QueryBuilderRow.tsx" export type QueryBuilderGroupProps = { combinatorOptions: readonly { label: string value: Combinator }[] createLeafValue: () => Leaf /** Root is `0`; nested groups deepen and gain the accent rail. */ depth: number labels: Required node: TreeGroupNode renderCombinator?: (args: { nodeId: string onChange: (combinator: Combinator) => void options: readonly { label: string value: Combinator }[] value: Combinator }) => ReactNode renderLeaf: (args: { nodeId: string onChange: (value: Leaf) => void value: Leaf }) => ReactNode state: TreeState tree: Tree } /** * One match group: a "Match" combinator picker, its children (leaf * rows and nested groups), and a toolbar to add either. Its own file * because it renders itself recursively for nested groups and is * itself rendered inside the parent's `.map` — the member-file case. * * The picker is `QueryBuilderCombinator`, a `Listbox` rather than the * native `Select` this shipped with — see that file for why it is a * component and how it stays generic over an opaque `Combinator`. */ export const QueryBuilderGroup = ({ combinatorOptions, createLeafValue, depth, labels, node, renderCombinator, renderLeaf, state, tree, }: QueryBuilderGroupProps): ReactNode => { const children = selectChildNodes(state, node.id) const setCombinator = (nextCombinator: Combinator) => { tree.setCombinator(node.id, nextCombinator) } const card = (
{renderCombinator ? ( renderCombinator({ nodeId: node.id, onChange: setCombinator, options: combinatorOptions, value: node.combinator, }) ) : ( )}
{depth === 0 ? null : ( { tree.removeNode(node.id) }} size="sm" > ✕ )}
{children.length === 0 ? null : (
{children.map((child) => child.kind === "group" ? ( ) : ( { tree.patchLeaf(child.id, value) }} onRemove={() => { tree.removeNode(child.id) }} renderLeaf={renderLeaf} value={child.value} /> ), )}
)}
) // Depth is shown with a semantic-token inline-start rail. The colour // utility paints all four sides, but only the inline-start edge has // a width, so only it renders — no raw palette colour anywhere. return depth === 0 ? ( card ) : (
{card}
) }