diff --git a/apps/catalog/src/CatalogApp.tsx b/apps/catalog/src/CatalogApp.tsx index 8c45003..4be11d9 100644 --- a/apps/catalog/src/CatalogApp.tsx +++ b/apps/catalog/src/CatalogApp.tsx @@ -376,6 +376,7 @@ export function CatalogApp() { const [workspaceWindowDemoOpen, setWorkspaceWindowDemoOpen] = useState(true); const [workspaceWindowDemoMaximized, setWorkspaceWindowDemoMaximized] = useState(false); const [workspaceWindowDemoRect, setWorkspaceWindowDemoRect] = useState({ x: 24, y: 24, width: 340, height: 230 }); + const [splitPaneInvisible, setSplitPaneInvisible] = useState(false); const [splitPaneDemoSize, setSplitPaneDemoSize] = useState(50); const [sidePanelDemoOpen, setSidePanelDemoOpen] = useState(true); const [createModalOpen, setCreateModalOpen] = useState(false); @@ -1510,9 +1511,11 @@ export function CatalogApp() {

Rectangle, maximize, close и stacking остаются состоянием приложения; дизайн-система владеет одинаковой bounded-геометрией и доступным управлением.

+
.nodedc-split-pane__separator:not(:focus-visible)::before { + background: transparent; + box-shadow: none; +} + .nodedc-split-pane[data-dragging="true"] { user-select: none; } diff --git a/packages/ui-react/src/SplitPane.tsx b/packages/ui-react/src/SplitPane.tsx index 06c636e..db234e9 100644 --- a/packages/ui-react/src/SplitPane.tsx +++ b/packages/ui-react/src/SplitPane.tsx @@ -23,6 +23,7 @@ export interface SplitPaneProps extends Omit, "ch step?: number; resizable?: boolean; separatorLabel: string; + separatorAppearance?: "line" | "invisible"; } const clamp = (value: number, minimum: number, maximum: number) => ( @@ -48,6 +49,7 @@ export function SplitPane({ step = 2, resizable = true, separatorLabel, + separatorAppearance = "line", className, style, ...props @@ -95,6 +97,7 @@ export function SplitPane({ ref={rootRef} className={cn("nodedc-split-pane", className)} data-orientation={orientation} + data-separator-appearance={separatorAppearance} data-dragging={dragging ? "true" : undefined} style={splitStyle} {...props} diff --git a/registry/components.json b/registry/components.json index dd8c648..9e7a7eb 100644 --- a/registry/components.json +++ b/registry/components.json @@ -288,7 +288,8 @@ "summary": "Controlled two-panel layout with a pointer- and keyboard-resizable accessible separator.", "anatomy": ["primary panel", "structural separator", "secondary panel"], "variants": ["vertical", "horizontal"], - "behavior": ["controlled percentage", "optional resizable separator", "pointer capture drag", "Arrow/Home/End keyboard sizing", "ARIA separator range", "ratio preservation across host resize"], + "behavior": ["controlled percentage", "optional resizable separator", + "line or invisible separator appearance with retained keyboard focus", "pointer capture drag", "Arrow/Home/End keyboard sizing", "ARIA separator range", "ratio preservation across host resize"], "rules": [ "The application owns the controlled primary percentage and panel content; the component owns separator interaction and accessibility.", "Minimum primary and secondary percentages clamp both pointer and keyboard changes.", diff --git a/scripts/split-pane-contract.test.mjs b/scripts/split-pane-contract.test.mjs index d698071..192e22b 100644 --- a/scripts/split-pane-contract.test.mjs +++ b/scripts/split-pane-contract.test.mjs @@ -61,3 +61,15 @@ test("SplitPane is registered, documented, cataloged and keyboard-addressable", assert.match(docs, /## SplitPane/); assert.match(catalog, / { + const markup = renderToStaticMarkup(createElement(SplitPane, { + primary: "Camera", secondary: "Map", primarySize: 40, + onPrimarySizeChange: () => {}, separatorLabel: "Resize", separatorAppearance: "invisible", + })); + assert.match(markup, /data-separator-appearance="invisible"/); + assert.match(markup, /role="separator"/); + assert.match(markup, /tabindex="0"/); + assert.match(markup, /aria-valuenow="40"/); +});