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 @@ -3,7 +3,7 @@ import styled from '@emotion/styled';
export const StyledWrapper = styled.div`
max-width: 100rem;
margin-inline: auto;
padding: 1rem 3rem;
padding: 1rem 5rem;

@container docs (max-width: 768px) {
padding: 0.9375rem 1.25rem;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, it, expect } from 'vitest';
import ActionIconButton from './ActionIconButton';
import { useRenderToDom } from '@/hooks/useRenderToDom';
import { getByTestId, query } from '@/test-utils/dom';

describe('ActionIconButton', () => {
it('renders a button with the accessible label and its child glyph', () => {
const root = useRenderToDom(
<ActionIconButton label="Toggle sidebar">
<svg data-testid="glyph" />
</ActionIconButton>
);
expect(getByTestId(root, 'glyph')).toBeDefined();
expect(query(root, 'button').getAttribute('aria-label')).toBe('Toggle sidebar');
});

it('forwards extra props (aria-expanded)', () => {
const root = useRenderToDom(
<ActionIconButton label="More options" aria-expanded={true}>
<svg />
</ActionIconButton>
);
expect(query(root, 'button').getAttribute('aria-expanded')).toBe('true');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { StyledWrapper } from './StyledWrapper';
import Tooltip from '@/ui/Tooltip/Tooltip';

export interface ActionIconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
/** Accessible label — icon buttons have no visible text. */
label: string;
/** Show the hover/focus tooltip carrying `label`. Defaults to `true`. */
showTooltip?: boolean;
}

const ActionIconButton = React.forwardRef<HTMLButtonElement, ActionIconButtonProps>(
({ label, children, type = 'button', showTooltip = true, ...rest }, ref) => (
<Tooltip content={label} disabled={!label || !showTooltip}>
<StyledWrapper ref={ref} type={type} aria-label={label} {...rest}>
{children}
</StyledWrapper>
</Tooltip>
)
);

export default ActionIconButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import styled from '@emotion/styled';

export const StyledWrapper = styled.button`
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.5rem;
height: 1.5rem;
padding: 0;
flex: none;
background: transparent;
color: var(--oc-text);
border: none;
border-radius: var(--oc-radius);
cursor: pointer;
transition: background-color 0.12s ease, color 0.12s ease;

&:hover {
background: var(--oc-background-surface0);
color: var(--oc-text);
}

&[aria-expanded='true'] {
background: var(--oc-background-surface0);
color: var(--oc-text);
}
`;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import IconButton from '@/ui/IconButton/IconButton';
import ActionIconButton from '../ActionIconButton/ActionIconButton';
import { IconLayoutColumns, IconLayoutRows } from '@tabler/icons';

interface ChangeLayoutProps {
Expand All @@ -9,13 +9,13 @@ interface ChangeLayoutProps {

const ChangeLayout: React.FC<ChangeLayoutProps> = ({ orientation, handleChangeLayout }) => {
return (
<IconButton label="Change Layout" className="p-1" onClick={handleChangeLayout}>
<ActionIconButton label="Change Layout" className="p-1" onClick={handleChangeLayout}>
{orientation === 'vertical' ? (
<IconLayoutColumns size={13} stroke={1.5} style={{ color: 'var(--text-muted)' }} />
<IconLayoutColumns size={16} stroke={1.5} style={{ color: 'var(--text-muted)' }} />
) : (
<IconLayoutRows size={13} stroke={1.5} style={{ color: 'var(--text-muted)' }} />
<IconLayoutRows size={16} stroke={1.5} style={{ color: 'var(--text-muted)' }} />
)}
</IconButton>
</ActionIconButton>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import React from 'react';
import IconButton from '@/ui/IconButton/IconButton';
import { IconEraser } from '@tabler/icons';
import ActionIconButton from '../ActionIconButton/ActionIconButton';

interface ClearResponseProps {
onClick: () => void;
}

const ClearResponse: React.FC<ClearResponseProps> = ({ onClick }) => {
return (
<IconButton label="Clear Response" className="p-1" onClick={onClick}>
<IconEraser size={13} stroke={1.5} style={{ color: 'var(--text-muted)' }} />
</IconButton>
<ActionIconButton label="Clear Response" className="p-1" onClick={onClick}>
<IconEraser size={16} stroke={1.5} style={{ color: 'var(--text-muted)' }} />
</ActionIconButton>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { IconCheck, IconCopy } from '@tabler/icons';
import IconButton from '@/ui/IconButton/IconButton';
import ActionIconButton from '../ActionIconButton/ActionIconButton';

interface CopyResponseProps {
copied: boolean;
Expand All @@ -10,9 +10,13 @@ interface CopyResponseProps {

const CopyResponse: React.FC<CopyResponseProps> = ({ copied, onClick, disabled }) => {
return (
<IconButton label="Copy Response" className="p-1" disabled={disabled} onClick={onClick}>
{copied ? <IconCheck size={13} stroke={1.5} style={{ color: 'var(--text-muted)' }} /> : <IconCopy size={13} stroke={1.5} style={{ color: 'var(--text-muted)' }} />}
</IconButton>
<ActionIconButton label="Copy Response" className="p-1" disabled={disabled} onClick={onClick}>
{copied ? (
<IconCheck size={16} stroke={1.5} style={{ color: 'var(--text-muted)' }} />
) : (
<IconCopy size={16} stroke={1.5} style={{ color: 'var(--text-muted)' }} />
)}
</ActionIconButton>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import IconButton from '@/ui/IconButton/IconButton';
import { IconDownload } from '@tabler/icons';
import ActionIconButton from '../ActionIconButton/ActionIconButton';

interface DownloadResponseProps {
onClick: () => void;
Expand All @@ -9,9 +10,9 @@ interface DownloadResponseProps {

const DownloadResponse: React.FC<DownloadResponseProps> = ({ onClick, disabled }) => {
return (
<IconButton label="Download Response" className="p-1" disabled={disabled} onClick={onClick}>
<IconDownload size={13} stroke={1.5} style={{ color: 'var(--text-muted)' }} />
</IconButton>
<ActionIconButton label="Download Response" className="p-1" disabled={disabled} onClick={onClick}>
<IconDownload size={16} stroke={1.5} style={{ color: 'var(--text-muted)' }} />
</ActionIconButton>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import ChangeLayout from './ChangeLayout/ChangeLayout';
import { StyledWrapper } from './StyledWrapper';
import MenuDropdown from '@/ui/MenuDropdown';
import type { MenuDropdownItem } from '@/ui/MenuDropdown';
import IconButton from '@/ui/IconButton/IconButton';
import ActionIconButton from './ActionIconButton/ActionIconButton';
import type { RunRequestResponse } from '@/runner';
import type { ResponseBodyFormat } from '@/constants';
import { useAppDispatch } from '@/store/hooks';
Expand Down Expand Up @@ -85,9 +85,9 @@ const ResponseActions: React.FC<ResponseActionsProps> = ({
<StyledWrapper className="response-pane-actions-wrapper" data-testid="response-pane-actions-wrapper">
<div className="actions-dropdown" data-testid="actions-dropdown">
<MenuDropdown items={menuItems} placement="bottom-end" testId="response-actions-menu">
<IconButton label="More actions" showTooltip={false} className="p-1">
<IconDots size={13} stroke={1.5} style={{ color: 'var(--text-muted)' }} />
</IconButton>
<ActionIconButton label="More actions" showTooltip={false} className="p-1 more-actions-button">
<IconDots size={16} stroke={1.5} style={{ color: 'var(--text-muted)' }} />
</ActionIconButton>
</MenuDropdown>
</div>
<div className="actions-buttons" data-testid="actions-buttons">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@ import styled from '@emotion/styled';
export const StyledWrapper = styled.div`
display: flex;
align-items: center;
gap: 0.5rem;

.actions-dropdown { display: flex; }
.actions-buttons { display: none; }

.more-actions-button {
border: 1px solid var(--oc-input-border);
height: 1.25rem;
}

.expandable & {
.actions-dropdown { display: none; }
.actions-buttons { display: flex; align-items: center; gap: 0.5rem; }
.actions-dropdown {
display: none;
}
.actions-buttons {
display: flex;
align-items: center;
gap: 0.125rem;
}
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ const ResponseDuration: React.FC<ResponseDurationProps> = ({ duration }) => {
};

return (
<div className="flex items-center gap-1">
<span className="font-mono" style={{ color: 'var(--text-primary)' }}>
{duration}ms
</span>
<div className="font-medium">
{duration}ms
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const ResponseSize: React.FC<ResponseSizeProps> = ({ size }) => {
);

return (
<div className="font-mono" style={{ color: 'var(--text-primary)' }}>
<div className="font-medium">
{sizeToDisplay}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const ResponseStatus: React.FC<ResponseStatusProps> = ({ status, statusText }) =

return (
<div
className="font-mono font-medium"
className="font-bold"
style={{
color: getStatusColor(status)
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,17 +114,17 @@ const ResponsePane: React.FC<ResponsePaneProps> = ({ response, isLoading, orient
// the responsive tab bar can measure the actions block (the last child) to decide whether to show
// it as inline buttons or collapse it into a menu.
const statusInfo = (
<>
<div className="flex items-center gap-3 flex-wrap text-xs">
{activeTab === 'response' && (
<ResponseFormatSelector
selectedFormat={selectedFormat}
allowedFormats={allowedFormats}
handleSelection={(value: ResponseBodyFormat) => handleFormatChange(value)}
showPreview={showPreview}
toggleView={toggleView}
/>
)}
<div className="flex items-center gap-3 flex-wrap text-xs">
{activeTab === 'response' && (
<ResponseFormatSelector
selectedFormat={selectedFormat}
allowedFormats={allowedFormats}
handleSelection={(value: ResponseBodyFormat) => handleFormatChange(value)}
showPreview={showPreview}
toggleView={toggleView}
/>
)}
<div className="flex items-center gap-2 flex-wrap">
<ResponseStatus status={response.status} statusText={response.statusText} />
<ResponseDuration duration={response.duration} />
<ResponseSize size={response.size} />
Expand All @@ -136,7 +136,7 @@ const ResponsePane: React.FC<ResponsePaneProps> = ({ response, isLoading, orient
selectedFormat={selectedFormat}
showPreview={showPreview}
/>
</>
</div>
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ const rowIndent = (level: number): string => `${0.5 + Math.min(level, 7) * 0.4}r
// The docs column's outer padding collapses at its `@container docs (max-width: 768px)` breakpoint,
// leaving no gutter — below it the rail hides rather than overlapping the content.
const DOCS_MIN_WIDTH = 768;
// The rail's fixed top (`top: 5rem`) plus a small gap; its height is capped at the docs area's
// The rail's fixed top (`top: 13rem`) plus a small gap; its height is capped at the docs area's
// bottom minus this so it clips at, rather than draws over, a bottom-docked playground.
const RAIL_TOP_PX = 88;
const RAIL_TOP_PX = 216;
// Below this remaining height the rail hides entirely rather than show a cramped/overlapping sliver.
const MIN_RAIL_HEIGHT = 24;

Expand Down
16 changes: 13 additions & 3 deletions packages/bruno-api-docs/src/components/SectionNav/StyledWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import styled from '@emotion/styled';

export const StyledWrapper = styled.div`
position: fixed;
top: 5rem;
top: 13rem;
right: 0;
z-index: calc(var(--z-overlay, 50) - 1);
font-family: var(--font-sans);
Expand Down Expand Up @@ -81,7 +81,18 @@ export const StyledWrapper = styled.div`
background: var(--bg-primary);
border-color: var(--border-color);
overflow-y: auto;
transition: background 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease;
animation: sectionNavOpen 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes sectionNavOpen {
from {
opacity: 0;
transform: translateX(1.25rem);
}
to {
opacity: 1;
transform: translateX(0);
}
}
&.section-nav--open .section-nav-item {
justify-content: flex-start;
Expand All @@ -93,7 +104,6 @@ export const StyledWrapper = styled.div`
opacity: 1;
white-space: normal;
overflow-wrap: break-word;
transition: max-width 0.18s ease, max-height 0.18s ease, opacity 0.18s ease;
}
&.section-nav--open .section-nav-tick-slot {
display: none;
Expand Down
2 changes: 1 addition & 1 deletion packages/bruno-api-docs/src/constants/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,4 @@ export const FORMAT_ICONS: Record<ResponseBodyFormat, TablerIcon> = {

// Width the actions block occupies when shown as buttons; the responsive tab bar uses it to
// decide whether to expand the actions inline or collapse them into a menu.
export const RESPONSE_ACTIONS_EXPANDED_WIDTH = 135;
export const RESPONSE_ACTIONS_EXPANDED_WIDTH = 302;
Loading