Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,8 @@ describe('NodeIOView', () => {
render(<NodeIOView schema={SCHEMA} value={{ statusCode: 200 }} />);
await userEvent.click(screen.getByRole('tab', { name: 'JSON' }));
expect(screen.getByText(/"statusCode": 200/)).toBeInTheDocument();
// Radix's tabindex="0" is dropped, so the browser decides whether the panel is a stop.
expect(screen.getByRole('tabpanel')).not.toHaveAttribute('tabindex');
});

it('hides the filter control unless filters are provided', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,13 @@ export function NodeIOView({
className="shrink-0"
/>

<TabsContent value="schema" className="mt-0 flex min-h-0 flex-1 flex-col">
{/* No tabIndex, so the browser makes a panel a tab stop only when it scrolls with nothing
focusable inside. Radix's default 0 would stop on every panel. */}
<TabsContent
value="schema"
tabIndex={undefined}
className="mt-0 flex min-h-0 flex-1 flex-col"
>
<div className="min-h-0 flex-1 overflow-hidden rounded-xl border border-surface-overlay bg-surface-overlay/40">
<JsonTreeView
nodes={nodes}
Expand Down Expand Up @@ -287,7 +293,11 @@ export function NodeIOView({
</div>
</TabsContent>

<TabsContent value="json" className="mt-0 flex min-h-0 flex-1 flex-col">
<TabsContent
value="json"
tabIndex={undefined}
className="mt-0 flex min-h-0 flex-1 flex-col"
>
<div className="min-h-0 flex-1 overflow-hidden rounded-xl border border-surface-overlay bg-surface-overlay/40">
{jsonView ?? (
<pre className="h-full overflow-auto p-3 font-mono text-xs leading-4.5 text-foreground">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,14 @@ describe('MetadataForm', () => {
expect(screen.queryByRole('tab', { name: 'Should Not Render' })).not.toBeInTheDocument();
});

it('makes the panel a tab stop only when the form is disabled', () => {
const { rerender } = render(<MetadataForm schema={tabbedSchema} stepVariant="tabs" />);
expect(screen.getByRole('tabpanel')).not.toHaveAttribute('tabindex');

rerender(<MetadataForm schema={tabbedSchema} stepVariant="tabs" disabled />);
expect(screen.getByRole('tabpanel')).toHaveAttribute('tabindex', '0');
});

it('keeps a value entered in one tab after switching tabs (single shared form instance)', async () => {
const user = userEvent.setup();
render(<MetadataForm schema={tabbedSchema} stepVariant="tabs" />);
Expand Down
3 changes: 3 additions & 0 deletions packages/apollo-wind/src/components/forms/metadata-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,9 @@ function TabbedStepForm({
key={step.id}
value={step.id}
padded={!selfPadded}
// Disabled fields leave nothing to focus, and Safari never makes a scroller a tab
// stop on its own, so keep one for keyboard scrolling.
tabIndex={disabled ? 0 : undefined}
innerClassName={showEmptyState ? 'text-sm text-muted-foreground' : 'space-y-2'}
>
{showEmptyState ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,11 @@ describe('JsonTreeView row decorations and actions', () => {

expect(screen.getByRole('button', { name: 'Focus' })).toHaveClass('opacity-100');
expect(screen.getByRole('button', { name: 'Add' })).not.toHaveClass('opacity-100');
// Also revealed while the row holds keyboard focus.
expect(screen.getByRole('button', { name: 'Add' })).toHaveClass(
'group-hover:opacity-100',
'group-focus-within:opacity-100'
);
});

it('orders the trailing cluster as start actions, meta, then end actions', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { JsonTreeTooltip } from './JsonTreeTooltip';
import type { JsonTreeNode, NodeAction, NodeDecorationTone } from './JsonTree.types';

const ACTION_BUTTON_CLASS =
'shrink-0 rounded text-foreground-subtle opacity-0 hover:text-foreground hover:bg-surface-raised group-hover:opacity-100 focus-visible:opacity-100';
'shrink-0 rounded text-foreground-subtle opacity-0 hover:text-foreground hover:bg-surface-raised group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100';

const ACTION_TONE_CLASS: Record<NodeDecorationTone, string> = {
neutral: '',
Expand Down
14 changes: 14 additions & 0 deletions packages/apollo-wind/src/components/ui/panel-tabs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ describe('PanelTabs', () => {
);
});

it('leaves the content tab stop to the browser', async () => {
const user = userEvent.setup();
render(<Panel />);

// Radix sets tabindex="0"; without it, only a scroller with nothing focusable is a stop.
expect(screen.getByRole('tabpanel')).not.toHaveAttribute('tabindex');
await user.click(screen.getByRole('tab', { name: 'Parameters' }));
await user.tab();
expect(screen.getByRole('button', { name: 'Notes' })).toHaveFocus();
// Tab from the last tab-row control skips the panel.
await user.tab();
expect(document.body).toHaveFocus();
});

it('offers the strip alone for rows that hold other controls', () => {
render(
<PanelTabs defaultValue="a">
Expand Down
7 changes: 6 additions & 1 deletion packages/apollo-wind/src/components/ui/panel-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@ interface PanelTabsContentProps extends React.ComponentPropsWithoutRef<typeof Ta
innerClassName?: string;
}

/** The active tab's content. It is the scroll container, so the tab row stays pinned. */
/**
* The active tab's content. It is the scroll container, so the tab row stays pinned.
* It has no tabIndex, so the browser makes it a tab stop only when it scrolls with
* nothing focusable inside. Radix's default 0 would stop on every panel.
*/
const PanelTabsContent = React.forwardRef<
React.ElementRef<typeof TabsContent>,
PanelTabsContentProps
Expand All @@ -128,6 +132,7 @@ const PanelTabsContent = React.forwardRef<
ref={ref}
data-slot="panel-tabs-content"
className={cn('mt-0 min-h-0 flex-1 overflow-auto', className)}
tabIndex={undefined}
{...props}
>
{padded ? (
Expand Down
Loading