Skip to content

Commit 4136033

Browse files
authored
feat(designer-ui): Add PinMenuItem component & styles (#5159)
* Add 'pin action' menu item (not displayed) * Update tests * Fix test name * Fix snapshot after updating test name
1 parent 53b7db7 commit 4136033

File tree

15 files changed

+300
-15
lines changed

15 files changed

+300
-15
lines changed

Localize/lang/strings.json

+4
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,7 @@
799799
"U7yRwM": "Validation Error",
800800
"UCNM4L": "To reference a parameter, use the dynamic content list.",
801801
"UEryJE": "Delete Workflow Action",
802+
"UFMpGk": "Unpin Action",
802803
"UHCVNK": "Replaces a string with a given string",
803804
"UMPuUJ": "Delete {expressionValue}",
804805
"UPsZSw": "The entered identity is not associated with this logic app.",
@@ -1763,6 +1764,7 @@
17631764
"_U7yRwM.comment": "The title of the validation error field in the static result parseJson action",
17641765
"_UCNM4L.comment": "Description for Workflow Parameters Part 2",
17651766
"_UEryJE.comment": "Title for operation node",
1767+
"_UFMpGk.comment": "Text indicating a menu button to unpin a pinned action from the side panel",
17661768
"_UHCVNK.comment": "Label for description of custom replace Function",
17671769
"_UMPuUJ.comment": "Label to delete a value",
17681770
"_UPsZSw.comment": "error message for invalid user",
@@ -2593,6 +2595,7 @@
25932595
"_yuJBmK.comment": "Placeholder description for a newly inserted Number parameter",
25942596
"_yucvJE.comment": "Time zone value ",
25952597
"_yxctQw.comment": "Label for connection reference logical name",
2598+
"_yy7jg4.comment": "Text indicating a menu button to pin an action to the side panel",
25962599
"_yzqXMp.comment": "The text for the 'Learn more' link.",
25972600
"_z/UY4k.comment": "a label that shows which folder the user will be able to dig deeper into",
25982601
"_z3VuE+.comment": "Menu label",
@@ -3285,6 +3288,7 @@
32853288
"yuJBmK": "Please enter a number",
32863289
"yucvJE": "(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb",
32873290
"yxctQw": "Logical name",
3291+
"yy7jg4": "Pin Action",
32883292
"yzqXMp": "Learn more",
32893293
"z/UY4k": "Navigate to {folderName} folder",
32903294
"z3VuE+": "Menu",

libs/designer-ui/src/lib/card/__test__/__snapshots__/card.spec.tsx.snap

+81
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,87 @@ exports[`lib/card > should render as inactive 1`] = `
337337
</div>
338338
`;
339339

340+
exports[`lib/card > should render as pinned 1`] = `
341+
<div
342+
style={
343+
{
344+
"position": "relative",
345+
}
346+
}
347+
>
348+
<div
349+
className="msla-panel-card-container"
350+
data-automation-id="card-title"
351+
data-testid="card-title"
352+
id="msla-node-id"
353+
onClick={[Function]}
354+
onContextMenu={[Function]}
355+
onKeyDown={[Function]}
356+
onKeyUp={[Function]}
357+
style={
358+
{
359+
"borderLeft": "4px solid rgba(71,71,71, 1)",
360+
"borderRadius": "2px",
361+
}
362+
}
363+
tabIndex={2}
364+
>
365+
<div
366+
className="msla-selection-box pinned"
367+
/>
368+
<div
369+
className="panel-card-main"
370+
>
371+
<div
372+
aria-label=" operation"
373+
className="panel-card-header"
374+
role="button"
375+
>
376+
<div
377+
className="panel-card-content-container"
378+
>
379+
<div
380+
className="panel-card-content-gripper-section"
381+
/>
382+
<div
383+
className="panel-card-content-icon-section"
384+
>
385+
<div
386+
className="fui-Spinner r1k3z50n msla-card-header-spinner"
387+
role="progressbar"
388+
>
389+
<span
390+
className="fui-Spinner__spinner rvgcg50 ___ruqntd0_k4fqbj0 fjamq6b f64fuq3 f1v3ph3m"
391+
>
392+
<span
393+
className="fui-Spinner__spinnerTail r1lregi5"
394+
/>
395+
</span>
396+
</div>
397+
</div>
398+
<div
399+
className="panel-card-top-content"
400+
>
401+
<div
402+
className="panel-msla-title"
403+
>
404+
title
405+
</div>
406+
</div>
407+
</div>
408+
</div>
409+
<div
410+
className="msla-card-v2-footer"
411+
>
412+
<div
413+
className="msla-badges"
414+
/>
415+
</div>
416+
</div>
417+
</div>
418+
</div>
419+
`;
420+
340421
exports[`lib/card > should render as selected 1`] = `
341422
<div
342423
style={

libs/designer-ui/src/lib/card/__test__/card.spec.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import type { CardProps } from '../index';
22
import { Card } from '../index';
3+
import React from 'react';
34
import renderer from 'react-test-renderer';
45
import { describe, vi, beforeEach, afterEach, beforeAll, afterAll, it, test, expect } from 'vitest';
6+
57
describe('lib/card', () => {
68
let minimal: CardProps;
79

@@ -39,7 +41,12 @@ describe('lib/card', () => {
3941
});
4042

4143
it('should render as selected', () => {
42-
const tree = renderer.create(<Card {...minimal} selected={true} />).toJSON();
44+
const tree = renderer.create(<Card {...minimal} selectionMode="selected" />).toJSON();
45+
expect(tree).toMatchSnapshot();
46+
});
47+
48+
it('should render as pinned', () => {
49+
const tree = renderer.create(<Card {...minimal} selectionMode="pinned" />).toJSON();
4350
expect(tree).toMatchSnapshot();
4451
});
4552

libs/designer-ui/src/lib/card/card.less

+7
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,9 @@
704704
outline-offset: -3px;
705705
}
706706
}
707+
&.pinned {
708+
border: 2px solid @pinned-item-color;
709+
}
707710
}
708711

709712
.msla-panel-card-container {
@@ -764,6 +767,10 @@
764767
}
765768
}
766769

770+
.msla-selection-box {
771+
border-left: 0;
772+
}
773+
767774
.panel-card-main {
768775
display: flex;
769776
flex-direction: column;

libs/designer-ui/src/lib/card/index.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export interface CardProps {
3838
operationName?: string;
3939
readOnly?: boolean;
4040
rootRef?: React.RefObject<HTMLDivElement>;
41-
selected?: boolean;
41+
selectionMode?: 'selected' | 'pinned' | false;
4242
staticResultsEnabled?: boolean;
4343
title: string;
4444
onClick?(): void;
@@ -82,7 +82,7 @@ export const Card: React.FC<CardProps> = ({
8282
isMonitoringView,
8383
isLoading,
8484
operationName,
85-
selected,
85+
selectionMode,
8686
staticResultsEnabled,
8787
title,
8888
onClick,
@@ -175,7 +175,7 @@ export const Card: React.FC<CardProps> = ({
175175
id={`msla-node-${id}`}
176176
className={css(
177177
'msla-panel-card-container',
178-
selected && 'msla-panel-card-container-selected',
178+
selectionMode === 'selected' && 'msla-panel-card-container-selected',
179179
!active && 'inactive',
180180
cloned && 'msla-card-ghost-image',
181181
isDragging && 'dragging'
@@ -199,7 +199,7 @@ export const Card: React.FC<CardProps> = ({
199199
resubmittedResults={runData?.executionMode === 'ResubmittedResults'}
200200
/>
201201
) : null}
202-
<div className={css('msla-selection-box', selected && 'selected')} />
202+
<div className={css('msla-selection-box', selectionMode)} />
203203
<div className="panel-card-main">
204204
<div aria-label={cardAltText} className="panel-card-header" role="button">
205205
<div className="panel-card-content-container">

libs/designer-ui/src/lib/card/scopeCard/index.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const ScopeCard: React.FC<ScopeCardProps> = ({
3434
onClick,
3535
onDeleteClick,
3636
handleCollapse,
37-
selected,
37+
selectionMode,
3838
contextMenuItems = [],
3939
runData,
4040
setFocus,
@@ -84,7 +84,7 @@ export const ScopeCard: React.FC<ScopeCardProps> = ({
8484
/>
8585
) : null}
8686
<div className="msla-scope-card-content">
87-
<div className={css('msla-selection-box', 'white-outline', selected && 'selected')} />
87+
<div className={css('msla-selection-box', 'white-outline', selectionMode)} />
8888
<button
8989
id={`msla-node-${id}`}
9090
className="msla-scope-card-title-button"

libs/designer-ui/src/lib/card/subgraphCard/index.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import NodeCollapseToggle from '../../nodeCollapseToggle';
33
import { CardContextMenu } from '../cardcontextmenu';
44
import { ErrorBanner } from '../errorbanner';
55
import { useCardContextMenu, useCardKeyboardInteraction } from '../hooks';
6+
import type { CardProps } from '..';
67
import type { MessageBarType } from '@fluentui/react';
78
import { css } from '@fluentui/react';
89
import type { SubgraphType } from '@microsoft/logic-apps-shared';
@@ -16,7 +17,7 @@ interface SubgraphCardProps {
1617
subgraphType: SubgraphType;
1718
collapsed?: boolean;
1819
handleCollapse?: () => void;
19-
selected?: boolean;
20+
selectionMode?: CardProps['selectionMode'];
2021
readOnly?: boolean;
2122
onClick?(id?: string): void;
2223
onDeleteClick?(): void;
@@ -33,7 +34,7 @@ export const SubgraphCard: React.FC<SubgraphCardProps> = ({
3334
subgraphType,
3435
collapsed,
3536
handleCollapse,
36-
selected = false,
37+
selectionMode = false,
3738
readOnly = false,
3839
onClick,
3940
onDeleteClick,
@@ -126,7 +127,7 @@ export const SubgraphCard: React.FC<SubgraphCardProps> = ({
126127
if (data.size === 'large') {
127128
return (
128129
<div className={css('msla-subgraph-card', data.size)} style={colorVars} tabIndex={-1}>
129-
<div className={css('msla-selection-box', 'white-outline', selected && 'selected')} tabIndex={-1} />
130+
<div className={css('msla-selection-box', 'white-outline', selectionMode)} tabIndex={-1} />
130131
<button
131132
id={`msla-node-${id}`}
132133
className="msla-subgraph-title"
@@ -162,7 +163,7 @@ export const SubgraphCard: React.FC<SubgraphCardProps> = ({
162163
onKeyDown={collapseKeyboardInteraction.keyUp}
163164
onKeyUp={collapseKeyboardInteraction.keyDown}
164165
>
165-
<div className={css('msla-selection-box', 'white-outline', selected && 'selected')} tabIndex={-1} />
166+
<div className={css('msla-selection-box', 'white-outline', selectionMode)} tabIndex={-1} />
166167
<div className="msla-subgraph-title msla-subgraph-title-text">{data.title}</div>
167168
<NodeCollapseToggle disabled collapsed={collapsed} onSmallCard />
168169
</div>

libs/designer-ui/src/lib/variables.less

+2
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@
132132
@brandColor: #0058ad;
133133
@brandColorLight: #3aa0f3;
134134

135+
@pinned-item-color: #f2610c;
136+
135137
@token-picker-offset-top: -150px;
136138

137139
@badge-width: 30px;

libs/designer/src/lib/core/state/panelV2/panelSelectors.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ import { useSelector } from 'react-redux';
55
const getPanelState = (state: RootState) => state.panelV2;
66

77
export const usePinnedNodeId = () => useSelector(createSelector(getPanelState, (state) => state.operationContent.pinnedNodeId ?? ''));
8+
9+
export const useIsNodePinned = (nodeId: string) => usePinnedNodeId() === nodeId;

libs/designer/src/lib/ui/CustomNodes/OperationCardNode.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import {
2121
useOperationVisuals,
2222
} from '../../core/state/operation/operationSelector';
2323
import { useIsNodeSelected } from '../../core/state/panel/panelSelectors';
24-
import { changePanelNode, selectPanelTab, setSelectedNodeId } from '../../core/state/panel/panelSlice';
24+
import { useIsNodePinned } from '../../core/state/panelV2/panelSelectors';
25+
import { changePanelNode, selectPanelTab, setSelectedNodeId } from '../../core/state/panelV2/panelSlice';
2526
import {
2627
useAllOperations,
2728
useConnectorName,
@@ -172,6 +173,7 @@ const DefaultNode = ({ targetPosition = Position.Top, sourcePosition = Position.
172173
);
173174

174175
const selected = useIsNodeSelected(id);
176+
const isPinned = useIsNodePinned(id);
175177
const nodeComment = useNodeDescription(id);
176178
const connectionResult = useNodeConnectionName(id);
177179
const isConnectionRequired = useIsConnectionRequired(operationInfo);
@@ -363,7 +365,7 @@ const DefaultNode = ({ targetPosition = Position.Top, sourcePosition = Position.
363365
onDeleteClick={deleteClick}
364366
onCopyClick={copyClick}
365367
operationName={operationSummary?.result}
366-
selected={selected}
368+
selectionMode={selected ? 'selected' : isPinned ? 'pinned' : false}
367369
contextMenuItems={contextMenuItems}
368370
setFocus={shouldFocus}
369371
staticResultsEnabled={!!staticResults}

libs/designer/src/lib/ui/CustomNodes/ScopeCardNode.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { CopyMenuItem } from '../menuItems';
4848
import { copyScopeOperation } from '../../core/actions/bjsworkflow/copypaste';
4949
import { Tooltip } from '@fluentui/react-components';
5050
import { useHotkeys } from 'react-hotkeys-hook';
51+
import { useIsNodePinned } from '../../core/state/panelV2/panelSelectors';
5152
import { RunAfterMenuItem } from '../menuItems/runAfterMenuItem';
5253
import { RUN_AFTER_PANEL_TAB } from './constants';
5354
import { shouldDisplayRunAfter } from './helpers';
@@ -155,6 +156,7 @@ const ScopeCardNode = ({ data, targetPosition = Position.Top, sourcePosition = P
155156
[readOnly, metadata]
156157
);
157158

159+
const isPinned = useIsNodePinned(scopeId);
158160
const selected = useIsNodeSelected(scopeId);
159161
const brandColor = useBrandColor(scopeId);
160162
const iconUri = useIconUri(scopeId);
@@ -345,7 +347,7 @@ const ScopeCardNode = ({ data, targetPosition = Position.Top, sourcePosition = P
345347
readOnly={readOnly}
346348
onClick={nodeClick}
347349
onDeleteClick={deleteClick}
348-
selected={selected}
350+
selectionMode={selected ? 'selected' : isPinned ? 'pinned' : false}
349351
contextMenuItems={contextMenuItems}
350352
runData={runData}
351353
commentBox={comment}

libs/designer/src/lib/ui/CustomNodes/SubgraphCardNode.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useMonitoringView, useReadOnly } from '../../core/state/designerOptions
77
import { setShowDeleteModal } from '../../core/state/designerView/designerViewSlice';
88
import { useIconUri, useParameterValidationErrors } from '../../core/state/operation/operationSelector';
99
import { useIsNodeSelected } from '../../core/state/panel/panelSelectors';
10+
import { useIsNodePinned } from '../../core/state/panelV2/panelSelectors';
1011
import { changePanelNode, setSelectedNodeId } from '../../core/state/panel/panelSlice';
1112
import {
1213
useActionMetadata,
@@ -38,6 +39,7 @@ const SubgraphCardNode = ({ data, targetPosition = Position.Top, sourcePosition
3839
const readOnly = useReadOnly();
3940
const dispatch = useDispatch<AppDispatch>();
4041

42+
const isPinned = useIsNodePinned(subgraphId);
4143
const selected = useIsNodeSelected(subgraphId);
4244
const isLeaf = useIsLeafNode(id);
4345
const metadata = useNodeMetadata(subgraphId);
@@ -133,7 +135,7 @@ const SubgraphCardNode = ({ data, targetPosition = Position.Top, sourcePosition
133135
parentId={metadata?.graphId}
134136
subgraphType={metadata.subgraphType}
135137
title={label}
136-
selected={selected}
138+
selectionMode={selected ? 'selected' : isPinned ? 'pinned' : false}
137139
readOnly={readOnly}
138140
onClick={subgraphClick}
139141
collapsed={graphCollapsed}

0 commit comments

Comments
 (0)