mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-06 07:21:16 +08:00
Merge pull request #18484 from storybookjs/ghengeveld/ap-1856-collapsible-group-ui
Interactions: Collapse child interactions
This commit is contained in:
commit
ef2bc81f30
@ -28,7 +28,12 @@ interface InteractionsPanelProps {
|
||||
active: boolean;
|
||||
controls: Controls;
|
||||
controlStates: ControlStates;
|
||||
interactions: (Call & { status?: CallStates })[];
|
||||
interactions: (Call & {
|
||||
status?: CallStates;
|
||||
childCallIds: Call['id'][];
|
||||
isCollapsed: boolean;
|
||||
toggleCollapsed: () => void;
|
||||
})[];
|
||||
fileName?: string;
|
||||
hasException?: boolean;
|
||||
isPlaying?: boolean;
|
||||
@ -97,6 +102,9 @@ export const AddonPanelPure: React.FC<InteractionsPanelProps> = React.memo(
|
||||
callsById={calls}
|
||||
controls={controls}
|
||||
controlStates={controlStates}
|
||||
childCallIds={call.childCallIds}
|
||||
isCollapsed={call.isCollapsed}
|
||||
toggleCollapsed={call.toggleCollapsed}
|
||||
pausedAt={pausedAt}
|
||||
/>
|
||||
))}
|
||||
@ -125,13 +133,32 @@ export const Panel: React.FC<AddonPanelProps> = (props) => {
|
||||
const [isPlaying, setPlaying] = React.useState(false);
|
||||
const [isRerunAnimating, setIsRerunAnimating] = React.useState(false);
|
||||
const [scrollTarget, setScrollTarget] = React.useState<HTMLElement>();
|
||||
const [collapsed, setCollapsed] = React.useState<Set<Call['id']>>(new Set());
|
||||
|
||||
// Calls are tracked in a ref so we don't needlessly rerender.
|
||||
const calls = React.useRef<Map<Call['id'], Omit<Call, 'status'>>>(new Map());
|
||||
const setCall = ({ status, ...call }: Call) => calls.current.set(call.id, call);
|
||||
|
||||
const [log, setLog] = React.useState<LogItem[]>([]);
|
||||
const interactions = log.map(({ callId, status }) => ({ ...calls.current.get(callId), status }));
|
||||
const childCallMap = new Map<Call['id'], Call['id'][]>();
|
||||
const interactions = log
|
||||
.filter((call) => {
|
||||
if (!call.parentId) return true;
|
||||
childCallMap.set(call.parentId, (childCallMap.get(call.parentId) || []).concat(call.callId));
|
||||
return !collapsed.has(call.parentId);
|
||||
})
|
||||
.map(({ callId, status }) => ({
|
||||
...calls.current.get(callId),
|
||||
status,
|
||||
childCallIds: childCallMap.get(callId),
|
||||
isCollapsed: collapsed.has(callId),
|
||||
toggleCollapsed: () =>
|
||||
setCollapsed((ids) => {
|
||||
if (ids.has(callId)) ids.delete(callId);
|
||||
else ids.add(callId);
|
||||
return new Set(ids);
|
||||
}),
|
||||
}));
|
||||
|
||||
const endRef = React.useRef();
|
||||
React.useEffect(() => {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { IconButton, Icons, TooltipNote, WithTooltip } from '@storybook/components';
|
||||
import { Call, CallStates, ControlStates } from '@storybook/instrumenter';
|
||||
import { styled, typography } from '@storybook/theming';
|
||||
import { transparentize } from 'polished';
|
||||
@ -55,9 +56,15 @@ const RowContainer = styled('div', {
|
||||
}
|
||||
);
|
||||
|
||||
const RowHeader = styled.div<{ disabled: boolean }>(({ theme, disabled }) => ({
|
||||
display: 'flex',
|
||||
'&:hover': disabled ? {} : { background: theme.background.hoverable },
|
||||
}));
|
||||
|
||||
const RowLabel = styled('button', { shouldForwardProp: (prop) => !['call'].includes(prop) })<
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement> & { call: Call }
|
||||
>(({ theme, disabled, call }) => ({
|
||||
flex: 1,
|
||||
display: 'grid',
|
||||
background: 'none',
|
||||
border: 0,
|
||||
@ -68,7 +75,6 @@ const RowLabel = styled('button', { shouldForwardProp: (prop) => !['call'].inclu
|
||||
padding: '8px 15px',
|
||||
textAlign: 'start',
|
||||
cursor: disabled || call.status === CallStates.ERROR ? 'default' : 'pointer',
|
||||
'&:hover': disabled ? {} : { background: theme.background.hoverable },
|
||||
'&:focus-visible': {
|
||||
outline: 0,
|
||||
boxShadow: `inset 3px 0 0 0 ${
|
||||
@ -81,6 +87,19 @@ const RowLabel = styled('button', { shouldForwardProp: (prop) => !['call'].inclu
|
||||
},
|
||||
}));
|
||||
|
||||
const RowActions = styled.div(({ theme }) => ({
|
||||
padding: 6,
|
||||
}));
|
||||
|
||||
export const StyledIconButton = styled(IconButton as any)(({ theme }) => ({
|
||||
color: theme.color.mediumdark,
|
||||
margin: '0 3px',
|
||||
}));
|
||||
|
||||
const Note = styled(TooltipNote)(({ theme }) => ({
|
||||
fontFamily: theme.typography.fonts.base,
|
||||
}));
|
||||
|
||||
const RowMessage = styled('div')(({ theme }) => ({
|
||||
padding: '8px 10px 8px 36px',
|
||||
fontSize: typography.size.s1,
|
||||
@ -112,29 +131,54 @@ export const Interaction = ({
|
||||
callsById,
|
||||
controls,
|
||||
controlStates,
|
||||
childCallIds,
|
||||
isCollapsed,
|
||||
toggleCollapsed,
|
||||
pausedAt,
|
||||
}: {
|
||||
call: Call;
|
||||
callsById: Map<Call['id'], Call>;
|
||||
controls: Controls;
|
||||
controlStates: ControlStates;
|
||||
childCallIds?: Call['id'][];
|
||||
isCollapsed: boolean;
|
||||
toggleCollapsed: () => void;
|
||||
pausedAt?: Call['id'];
|
||||
}) => {
|
||||
const [isHovered, setIsHovered] = React.useState(false);
|
||||
return (
|
||||
<RowContainer call={call} pausedAt={pausedAt}>
|
||||
<RowLabel
|
||||
call={call}
|
||||
onClick={() => controls.goto(call.id)}
|
||||
disabled={!controlStates.goto || !call.interceptable || !!call.parentId}
|
||||
onMouseEnter={() => controlStates.goto && setIsHovered(true)}
|
||||
onMouseLeave={() => controlStates.goto && setIsHovered(false)}
|
||||
>
|
||||
<StatusIcon status={isHovered ? CallStates.ACTIVE : call.status} />
|
||||
<MethodCallWrapper style={{ marginLeft: 6, marginBottom: 1 }}>
|
||||
<MethodCall call={call} callsById={callsById} />
|
||||
</MethodCallWrapper>
|
||||
</RowLabel>
|
||||
<RowHeader disabled={!controlStates.goto || !call.interceptable || !!call.parentId}>
|
||||
<RowLabel
|
||||
call={call}
|
||||
onClick={() => controls.goto(call.id)}
|
||||
disabled={!controlStates.goto || !call.interceptable || !!call.parentId}
|
||||
onMouseEnter={() => controlStates.goto && setIsHovered(true)}
|
||||
onMouseLeave={() => controlStates.goto && setIsHovered(false)}
|
||||
>
|
||||
<StatusIcon status={isHovered ? CallStates.ACTIVE : call.status} />
|
||||
<MethodCallWrapper style={{ marginLeft: 6, marginBottom: 1 }}>
|
||||
<MethodCall call={call} callsById={callsById} />
|
||||
</MethodCallWrapper>
|
||||
</RowLabel>
|
||||
<RowActions>
|
||||
{childCallIds?.length > 0 && (
|
||||
<WithTooltip
|
||||
hasChrome={false}
|
||||
tooltip={
|
||||
<Note
|
||||
note={`${isCollapsed ? 'Show' : 'Hide'} interactions (${childCallIds.length})`}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<StyledIconButton containsIcon onClick={toggleCollapsed}>
|
||||
<Icons icon="listunordered" />
|
||||
</StyledIconButton>
|
||||
</WithTooltip>
|
||||
)}
|
||||
</RowActions>
|
||||
</RowHeader>
|
||||
|
||||
{call.status === CallStates.ERROR && call.exception?.callId === call.id && (
|
||||
<Exception exception={call.exception} />
|
||||
)}
|
||||
|
@ -272,7 +272,11 @@ export const ClassNode = ({ name }: { name: string }) => {
|
||||
|
||||
export const FunctionNode = ({ name }: { name: string }) => {
|
||||
const colors = useThemeColors();
|
||||
return <span style={{ color: colors.function }}>{name || <i>anonymous</i>}</span>;
|
||||
return name ? (
|
||||
<span style={{ color: colors.function }}>{name}</span>
|
||||
) : (
|
||||
<span style={{ color: colors.nullish, fontStyle: 'italic' }}>anonymous</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const ElementNode = ({
|
||||
|
Loading…
x
Reference in New Issue
Block a user