Skip to content

Commit 55aa072

Browse files
committed
WIP feat: make link plugins configuration only
* prevents plugin authors from having to use IBM Carbon components for link plugins
1 parent d92d26f commit 55aa072

File tree

2 files changed

+108
-54
lines changed

2 files changed

+108
-54
lines changed

demo/plugins/RPA.jsx

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,17 @@ export const RPATab = () => {
2121
};
2222

2323
export const RPALink = () => {
24-
const render = useCallback(({ output }) => {
25-
if (!output.variables?.RPA_Result) {
26-
return null;
27-
}
24+
const render = useCallback(() => 'View script', []);
2825

29-
return (
30-
<a
31-
href="https://camunda.com"
32-
target="_blank"
33-
rel="noreferrer"
34-
>
35-
View Script
36-
</a>
37-
);
38-
});
26+
const getVisible = useCallback(({ output }) => {
27+
return !!output?.variables?.RPA_Result;
28+
}, []);
3929

4030
return <TaskTesting.Link
4131
priority={ 10000 }
42-
key={ 'RPA_ScriptLink' }
32+
href="https://camunda.com"
33+
target="_blank"
34+
visible={ getVisible }
4335
render={ render }
4436
/>;
4537
};

lib/components/Output/Output.jsx

Lines changed: 101 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
InProgress
1313
} from '@carbon/icons-react';
1414

15-
import classNames from 'classnames';
15+
import { isFunction } from 'min-dash';
1616

1717
import { OutputVariables } from './OutputVariables';
1818
import { PluginContext } from '../shared/plugins';
@@ -139,77 +139,139 @@ const HeaderLinks = (props) => {
139139

140140
const headerLinkPlugins = getPlugins('output.header.link');
141141

142+
const headerLinks = headerLinkPlugins.reduce((accHeaderLinks, plugin) => {
143+
const getProp = (value) => isFunction(value) ? value(props) : value;
144+
145+
const visible = getProp(plugin.visible) ?? true;
146+
147+
if (!visible) {
148+
return accHeaderLinks;
149+
}
150+
151+
const content = plugin.render(props);
152+
153+
accHeaderLinks.push({
154+
content,
155+
href: getProp(plugin.href),
156+
target: getProp(plugin.target),
157+
className: getProp(plugin.className),
158+
onClick: getProp(plugin.onClick),
159+
role: getProp(plugin.role),
160+
tooltip: getProp(plugin.tooltip)
161+
});
162+
163+
return accHeaderLinks;
164+
}, /** @type {Array<{content: any, href: any, target: any, className: any, onClick: any, role: any, tooltip: any}>} */ ([]));
165+
142166
return (
143167
<>
144-
{ headerLinkPlugins.map((link, index) => (
145-
<React.Fragment key={ index }>
146-
{ link.render(props) }
147-
</React.Fragment>
148-
)) }
168+
{ headerLinks.map(({ content, href, target, className, onClick, role, tooltip }, index) => {
169+
const headerLink = (
170+
<Link
171+
key={ index }
172+
href={ href }
173+
target={ target }
174+
className={ className }
175+
onClick={ onClick }
176+
role={ role }
177+
>
178+
{ content }
179+
</Link>
180+
);
181+
182+
if (tooltip) {
183+
return (
184+
<Tooltip key={ index } autoAlign label={ tooltip }>
185+
{ headerLink }
186+
</Tooltip>
187+
);
188+
}
189+
190+
return headerLink;
191+
}) }
149192
</>
150193
);
151194
};
152195

153-
154-
export const HeaderLink = ({ children = null, render, priority = 100 }) => {
196+
/**
197+
* @param {Object} props
198+
* @param {(props: Object) => React.ReactNode} props.render - Function that returns link content
199+
* @param {boolean | ((props: Object) => boolean)} [props.visible] - Whether to show the link (default: true)
200+
* @param {string | ((props: Object) => string)} [props.href] - Link URL (static or dynamic)
201+
* @param {string | ((props: Object) => string)} [props.target] - Link target (static or dynamic)
202+
* @param {string | ((props: Object) => string | undefined)} [props.className] - Link class name (static or dynamic)
203+
* @param {Function | ((props: Object) => Function)} [props.onClick] - Click handler (static or dynamic)
204+
* @param {string | ((props: Object) => string)} [props.role] - ARIA role (static or dynamic)
205+
* @param {string | ((props: Object) => string | undefined)} [props.tooltip] - Tooltip text (static or dynamic)
206+
* @param {number} [props.priority] - Priority for sorting (higher values first)
207+
* @returns {null}
208+
*/
209+
export const HeaderLink = ({ render, visible, href, target, className, onClick, role, tooltip, priority = 100 }) => {
155210
const { registerPlugin, unregisterPlugin } = useContext(PluginContext);
156211

157212
useEffect(() => {
158-
const link = { render, children, priority, type: 'output.header.link' };
213+
const link = { render, visible, href, target, className, onClick, role, tooltip, priority, type: 'output.header.link' };
159214
registerPlugin(link);
160215

161216
return () => {
162217
unregisterPlugin(link);
163218
};
164-
}, [ children, render, priority, registerPlugin, unregisterPlugin ]);
219+
}, [ render, visible, href, target, className, onClick, role, tooltip, priority, registerPlugin, unregisterPlugin ]);
165220

166221
return null;
167222
};
168223

169224
const OperateLink = () => {
170-
const render = useCallback(({ output, isConnectionConfigured, currentOperateUrl }) => {
171-
const showOperateUrl = isConnectionConfigured && (currentOperateUrl || (output && !output.error));
225+
const render = useCallback(() => 'View in Operate', []);
172226

173-
if (!showOperateUrl) {
174-
return null;
175-
}
227+
const getVisible = useCallback(({ output, isConnectionConfigured, currentOperateUrl }) => {
228+
return isConnectionConfigured && (currentOperateUrl || (output && !output.error));
229+
}, []);
176230

231+
const getHref = useCallback(({ output, currentOperateUrl }) => {
232+
return currentOperateUrl || output?.operateUrl;
233+
}, []);
234+
235+
const getClassName = useCallback(({ output, currentOperateUrl }) => {
177236
const operateUrl = currentOperateUrl || output?.operateUrl;
237+
return !operateUrl ? 'link--disabled' : undefined;
238+
}, []);
178239

179-
return (
180-
<Tooltip
181-
className={ classNames({ 'show-tooltip': !operateUrl }) }
182-
autoAlign
183-
label={ NO_OPERATE_URL_TOOLTIP }
184-
>
185-
<Link
186-
className={ classNames({ 'link--disabled': !operateUrl }) }
187-
href={ operateUrl }
188-
target="_blank"
189-
>
190-
View in Operate
191-
</Link>
192-
</Tooltip>
193-
);
240+
const getTooltip = useCallback(({ output, currentOperateUrl }) => {
241+
const operateUrl = currentOperateUrl || output?.operateUrl;
242+
return !operateUrl ? NO_OPERATE_URL_TOOLTIP : undefined;
194243
}, []);
195244

196-
return <HeaderLink priority={ 200 } render={ render } />;
245+
return <HeaderLink
246+
priority={ 200 }
247+
render={ render }
248+
visible={ getVisible }
249+
href={ getHref }
250+
target="_blank"
251+
className={ getClassName }
252+
tooltip={ getTooltip }
253+
/>;
197254
};
198255

199256

200257
function ResetButton() {
258+
const render = useCallback(() => 'Clear', []);
201259

202-
const render = useCallback(({ onResetOutput, isConnectionConfigured, output }) => {
203-
const showResetButton = isConnectionConfigured && output;
260+
const getVisible = useCallback(({ isConnectionConfigured, output }) => {
261+
return isConnectionConfigured && output;
262+
}, []);
204263

205-
return showResetButton && <Link
206-
onClick={ () => onResetOutput() }
207-
role="button">
208-
Clear
209-
</Link>;
264+
const getOnClick = useCallback(({ onResetOutput }) => {
265+
return () => onResetOutput();
210266
}, []);
211267

212-
return <HeaderLink priority={ 100 } render={ render } />;
268+
return <HeaderLink
269+
priority={ 100 }
270+
render={ render }
271+
visible={ getVisible }
272+
onClick={ getOnClick }
273+
role="button"
274+
/>;
213275
}
214276

215277

0 commit comments

Comments
 (0)