feat(custom-widgets): complete Custom JSX v2 (#6390)

This commit is contained in:
Thomas authored and ajnart committed 2026-08-13 19:48:57 +02:00
1 parent 3a1245cbc5
commit acdfcbb18e
416 files changed
+37853 -18066

No files matched your search

@@ -1,8 +1,8 @@
import { useMemo, useState } from "react";
import { Badge, Button, Group, Popover, ScrollArea, Stack, Text, TextInput } from "@mantine/core";
import { Accordion, Badge, Button, Code, Group, Popover, ScrollArea, Stack, Text, TextInput } from "@mantine/core";
import { IconComponents, IconSearch } from "@tabler/icons-react";
import { enabledCustomJsxComponents } from "../core/component-registry";
import { customJsxAuthoringCatalog, getCustomJsxComponentProps } from "../core/component-catalog";
export interface ComponentReferenceMessages {
action: string;
@@ -11,19 +11,26 @@ export interface ComponentReferenceMessages {
count(count: number): string;
}
const enabledComponents = customJsxAuthoringCatalog.components.filter((component) => component.safety !== "denied");
export function ComponentReference({ messages }: { messages: ComponentReferenceMessages }) {
const [query, setQuery] = useState("");
const components = useMemo(() => {
const normalized = query.trim().toLocaleLowerCase();
return enabledCustomJsxComponents.filter(
(component) =>
normalized.length === 0 ||
component.name.toLocaleLowerCase().includes(normalized) ||
component.category.toLocaleLowerCase().includes(normalized),
if (!normalized) return enabledComponents;
return enabledComponents.filter((component) =>
[
component.name,
component.package,
component.category,
component.description ?? "",
...getCustomJsxComponentProps(component.name).map(({ name, type }) => `${name} ${type}`),
].some((value) => value.toLocaleLowerCase().includes(normalized)),
);
}, [query]);
return (
<Popover width={320} position="bottom-end" withinPortal shadow="md">
<Popover width={560} position="bottom-end" withinPortal shadow="md">
<Popover.Target>
<Button type="button" size="compact-xs" variant="subtle" leftSection={<IconComponents size={14} />}>
{messages.action}
@@ -39,27 +46,66 @@ export function ComponentReference({ messages }: { messages: ComponentReferenceM
aria-label={messages.search}
leftSection={<IconSearch size={14} />}
/>
<ScrollArea.Autosize mah={300} type="auto">
<Stack gap={4}>
{components.map((component) => (
<Group key={component.name} justify="space-between" wrap="nowrap" py={3} px={4}>
<Text size="xs" ff="monospace" fw={600}>
{component.name}
</Text>
<Badge size="xs" variant="light" color={component.safety === "wrapped" ? "yellow" : "gray"}>
{component.category}
</Badge>
</Group>
))}
{components.length === 0 && (
<Text size="xs" c="dimmed" ta="center" py="md">
{messages.empty}
</Text>
)}
</Stack>
<ScrollArea.Autosize mah={440} type="auto">
<Accordion variant="separated" radius="sm">
{components.map((component) => {
const props = getCustomJsxComponentProps(component.name);
return (
<Accordion.Item key={component.name} value={component.name}>
<Accordion.Control>
<Group justify="space-between" wrap="nowrap" pr="xs">
<Code>{component.name}</Code>
<Group gap={4} wrap="nowrap">
{component.bind && <Badge size="xs">bind: {component.bind.type}</Badge>}
<Badge size="xs" variant="light" color={component.safety === "wrapped" ? "yellow" : "gray"}>
{component.category}
</Badge>
</Group>
</Group>
</Accordion.Control>
<Accordion.Panel>
<Stack gap={6}>
{component.description && <Text size="xs">{component.description}</Text>}
<Text size="xs" c="dimmed">
{component.package} · {props.length} props
</Text>
{component.blockedProps.map((prop) => (
<Text key={prop.name} size="xs" c="red">
{prop.name}: {prop.reason}
</Text>
))}
<ScrollArea.Autosize mah={200} type="auto">
<Stack gap={3}>
{props.map((prop) => (
<Group key={prop.name} gap="xs" justify="space-between" wrap="nowrap">
<Code>{prop.name}</Code>
<Text size="xs" ff="monospace" c="dimmed" ta="right">
{prop.type}
{prop.required ? " · required" : ""}
</Text>
</Group>
))}
</Stack>
</ScrollArea.Autosize>
{component.accessibilityRequirements.map((requirement) => (
<Text key={requirement} size="xs" c="dimmed">
{requirement}
</Text>
))}
</Stack>
</Accordion.Panel>
</Accordion.Item>
);
})}
</Accordion>
{components.length === 0 && (
<Text size="xs" c="dimmed" ta="center" py="md">
{messages.empty}
</Text>
)}
</ScrollArea.Autosize>
<Text size="xs" c="dimmed">
{messages.count(enabledCustomJsxComponents.length)}
{messages.count(enabledComponents.length)} · Mantine {customJsxAuthoringCatalog.mantineVersion}
</Text>
</Stack>
</Popover.Dropdown>