Skip to content

Commit fbc6e3e

Browse files
committed
fix: revert StatusTransitionMenu changes
1 parent f5866d0 commit fbc6e3e

File tree

1 file changed

+148
-177
lines changed

1 file changed

+148
-177
lines changed
Lines changed: 148 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -1,183 +1,154 @@
1+
import { LoadingButton } from '@atlaskit/button';
2+
import DropdownMenu, { DropdownItem } from '@atlaskit/dropdown-menu';
3+
import ChevronDownIcon from '@atlaskit/icon/glyph/chevron-down';
4+
import Lozenge from '@atlaskit/lozenge';
15
import { Status, Transition } from '@atlassianlabs/jira-pi-common-models';
2-
import { fireEvent, render } from '@testing-library/react';
6+
import { Box } from '@material-ui/core';
37
import React from 'react';
48

5-
import { StatusTransitionMenu } from './StatusTransitionMenu';
6-
7-
describe('StatusTransitionMenu', () => {
8-
const mockTransitions: Transition[] = [
9-
{
10-
name: 'In Progress',
11-
to: {
12-
name: 'In Progress',
13-
statusCategory: {
14-
colorName: 'yellow',
15-
id: 0,
16-
key: '',
17-
name: '',
18-
self: '',
19-
},
20-
description: '',
21-
iconUrl: '',
22-
id: '',
23-
self: '',
24-
},
25-
hasScreen: false,
26-
id: '0',
27-
isConditional: false,
28-
isGlobal: false,
29-
isInitial: false,
30-
},
31-
{
32-
name: 'Done',
33-
to: {
34-
name: 'Done',
35-
statusCategory: {
36-
colorName: 'green',
37-
id: 0,
38-
key: '',
39-
name: '',
40-
self: '',
41-
},
42-
description: '',
43-
iconUrl: '',
44-
id: '',
45-
self: '',
46-
},
47-
hasScreen: false,
48-
id: '1',
49-
isConditional: false,
50-
isGlobal: false,
51-
isInitial: false,
52-
},
53-
];
54-
55-
const mockCurrentStatus: Status = {
56-
name: 'To Do',
57-
statusCategory: {
58-
colorName: 'blue-gray',
59-
id: 0,
60-
key: '',
61-
name: '',
62-
self: '',
63-
},
64-
description: '',
65-
iconUrl: '',
66-
id: '2',
67-
self: '',
68-
};
69-
70-
const mockOnStatusChange = jest.fn();
71-
72-
it('renders the current status name', () => {
73-
const { getByText } = render(
74-
<StatusTransitionMenu
75-
transitions={mockTransitions}
76-
currentStatus={mockCurrentStatus}
77-
isStatusButtonLoading={false}
78-
onStatusChange={mockOnStatusChange}
79-
/>,
80-
);
81-
82-
expect(getByText('To Do')).toBeTruthy();
83-
});
84-
85-
it('displays the dropdown menu when clicked', () => {
86-
const { getByText, queryByText } = render(
87-
<StatusTransitionMenu
88-
transitions={mockTransitions}
89-
currentStatus={mockCurrentStatus}
90-
isStatusButtonLoading={false}
91-
onStatusChange={mockOnStatusChange}
92-
/>,
93-
);
94-
95-
fireEvent.click(getByText('To Do'));
96-
expect(queryByText('In Progress')).toBeTruthy();
97-
expect(queryByText('Done')).toBeTruthy();
98-
});
99-
100-
it('calls onStatusChange when a transition is selected', () => {
101-
const { getByText } = render(
102-
<StatusTransitionMenu
103-
transitions={mockTransitions}
104-
currentStatus={mockCurrentStatus}
105-
isStatusButtonLoading={false}
106-
onStatusChange={mockOnStatusChange}
107-
/>,
108-
);
109-
110-
fireEvent.click(getByText('To Do'));
111-
fireEvent.click(getByText('In Progress'));
112-
113-
expect(mockOnStatusChange).toHaveBeenCalledWith(mockTransitions[0]);
9+
import { colorToLozengeAppearanceMap } from '../../../colors';
10+
11+
const statusCategoryOrder: Record<string, number> = {
12+
new: 1,
13+
indeterminate: 2,
14+
done: 3,
15+
};
16+
17+
// Sort transitions by status category (new → indeterminate → done)
18+
const sortTransitionsByStatusCategory = (transitions: Transition[]): Transition[] =>
19+
[...transitions].sort((a, b) => {
20+
const aOrder = statusCategoryOrder[a.to.statusCategory.key] ?? transitions.length;
21+
const bOrder = statusCategoryOrder[b.to.statusCategory.key] ?? transitions.length;
22+
if (aOrder !== bOrder) {
23+
return aOrder - bOrder;
24+
}
25+
return parseInt(a.to.id) - parseInt(b.to.id);
11426
});
11527

116-
it('displays the transition name if it differs from the target status name', () => {
117-
const transitionsWithDifferentNames: Transition[] = [
118-
{
119-
name: 'Start Progress',
120-
to: {
121-
name: 'In Progress',
122-
statusCategory: {
123-
colorName: 'yellow',
124-
id: 0,
125-
key: '',
126-
name: '',
127-
self: '',
28+
const StatusOption = (data: Transition) => (
29+
<Box>
30+
<Lozenge appearance={colorToLozengeAppearanceMap[data.to.statusCategory.colorName]}>{data.to.name}</Lozenge>
31+
</Box>
32+
);
33+
34+
const StatusOptionWithTransitionName = (data: Transition) => (
35+
<Box>
36+
{`${data.name} → `}
37+
<Lozenge appearance={colorToLozengeAppearanceMap[data.to.statusCategory.colorName]}>{data.to.name}</Lozenge>
38+
</Box>
39+
);
40+
41+
type Props = {
42+
transitions: Transition[];
43+
currentStatus: Status;
44+
isStatusButtonLoading: boolean;
45+
onStatusChange: (item: Transition) => void;
46+
};
47+
48+
export const StatusTransitionMenu: React.FC<Props> = (props) => {
49+
const [isHovered, setIsHovered] = React.useState(false);
50+
const [isOpen, setIsOpen] = React.useState(false);
51+
52+
const { border, background } = getDynamicStyles(props.currentStatus.statusCategory.colorName);
53+
const hasTransitions = props?.transitions?.length > 0;
54+
const transitionsSortedByCategory = sortTransitionsByStatusCategory(props.transitions);
55+
const shouldShowTransitionName = props.transitions.some((t) => t.name !== t.to.name);
56+
57+
const dropdownContent = hasTransitions ? (
58+
<Box
59+
data-testid="issue.status-transition-menu-dropdown"
60+
style={{
61+
display: 'flex',
62+
flexDirection: 'column',
63+
backgroundColor: 'var(--vscode-settings-textInputBackground)',
64+
paddingTop: '4px',
65+
paddingBottom: '4px',
66+
border: '1px solid var(--vscode-list-focusOutline)',
67+
}}
68+
>
69+
{transitionsSortedByCategory.map((t) => (
70+
<DropdownItem
71+
key={t.id}
72+
css={{
73+
':hover': {
74+
background: 'var(--vscode-editor-selectionHighlightBackground) !important',
75+
},
76+
}}
77+
onClick={() => props.onStatusChange(t)}
78+
>
79+
{shouldShowTransitionName ? StatusOptionWithTransitionName(t) : StatusOption(t)}
80+
</DropdownItem>
81+
))}
82+
</Box>
83+
) : null;
84+
85+
return (
86+
<Box
87+
style={{
88+
display: 'flex',
89+
}}
90+
>
91+
<DropdownMenu<HTMLButtonElement>
92+
css={{
93+
backgroundColor: 'var(--vscode-settings-textInputBackground)!important',
94+
':hover': {
95+
backgroundColor: 'var(--vscode-editor-selectionHighlightBackground)!important',
12896
},
129-
description: '',
130-
iconUrl: '',
131-
id: '',
132-
self: '',
133-
},
134-
hasScreen: false,
135-
id: '',
136-
isConditional: false,
137-
isGlobal: false,
138-
isInitial: false,
139-
},
140-
];
141-
142-
const { getByText } = render(
143-
<StatusTransitionMenu
144-
transitions={transitionsWithDifferentNames}
145-
currentStatus={mockCurrentStatus}
146-
isStatusButtonLoading={false}
147-
onStatusChange={mockOnStatusChange}
148-
/>,
149-
);
150-
151-
fireEvent.click(getByText('To Do'));
152-
expect(getByText('Start Progress →')).toBeTruthy();
153-
expect(getByText('In Progress')).toBeTruthy();
154-
});
155-
156-
it('Renders the dropdown arrow icon next to the current status', () => {
157-
const { getByRole } = render(
158-
<StatusTransitionMenu
159-
transitions={mockTransitions}
160-
currentStatus={mockCurrentStatus}
161-
isStatusButtonLoading={false}
162-
onStatusChange={mockOnStatusChange}
163-
/>,
164-
);
165-
166-
const statusButton = getByRole('button', { name: 'To Do Status' });
167-
expect(statusButton).toHaveProperty('disabled', false);
168-
expect(getByRole('img', { name: 'Status' })).toBeTruthy();
169-
});
170-
171-
it('disables dropdown when there are no transitions', () => {
172-
const { getByRole } = render(
173-
<StatusTransitionMenu
174-
transitions={[]}
175-
currentStatus={mockCurrentStatus}
176-
isStatusButtonLoading={false}
177-
onStatusChange={mockOnStatusChange}
178-
/>,
179-
);
180-
const statusButton = getByRole('button', { name: 'To Do' });
181-
expect(statusButton).toHaveProperty('disabled', true);
182-
});
183-
});
97+
}}
98+
onOpenChange={(open) => setIsOpen(open.isOpen)}
99+
isLoading={props.isStatusButtonLoading}
100+
trigger={({ triggerRef, ...properties }) => (
101+
<LoadingButton
102+
isLoading={props.isStatusButtonLoading}
103+
isDisabled={!hasTransitions}
104+
onMouseOver={() => setIsHovered(true)}
105+
onMouseLeave={() => setIsHovered(false)}
106+
style={{
107+
alignContent: 'center',
108+
border:
109+
(isOpen || isHovered) && hasTransitions
110+
? '1px solid var(--vscode-list-focusOutline)'
111+
: border,
112+
backgroundColor: background,
113+
114+
color: 'var(--vscode-editor-foreground)',
115+
}}
116+
{...properties}
117+
ref={triggerRef}
118+
iconAfter={hasTransitions ? <ChevronDownIcon label="Status" /> : undefined}
119+
>
120+
{props.currentStatus.name}
121+
</LoadingButton>
122+
)}
123+
>
124+
{dropdownContent}
125+
</DropdownMenu>
126+
</Box>
127+
);
128+
};
129+
130+
const getDynamicStyles = (colorName: string) => {
131+
let fields = { border: '', bg: '' };
132+
if (!statusCategoryToColorTokenMap[colorName]) {
133+
fields = statusCategoryToColorTokenMap['default'];
134+
} else {
135+
fields = statusCategoryToColorTokenMap[colorName];
136+
}
137+
return {
138+
border: `1px solid ${fields.border}`,
139+
background: fields.bg,
140+
};
141+
};
142+
143+
const statusCategoryToColorTokenMap: { [key: string]: { border: string; bg: string } } = {
144+
yellow: { border: '#669DF1', bg: '#669DF133' },
145+
green: { border: '#94C748', bg: '#94C74833' },
146+
'blue-gray': {
147+
border: '#B0BEC5',
148+
bg: '#B0BEC533',
149+
},
150+
default: {
151+
border: '#B0BEC5',
152+
bg: '#B0BEC533',
153+
},
154+
};

0 commit comments

Comments
 (0)