Skip to content

Commit bc3821e

Browse files
Add Help button to Domain Page (#899)
- Add Help button to Domain Page, with useful links - Gate it behind the Extended Domain Info feature flag for now - Fix typo in CLI commands modal - Set default border radius for popovers to 12px
1 parent da1b630 commit bc3821e

15 files changed

+498
-3
lines changed

src/config/theme/theme-light.config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const themeLight = {
1818
},
1919
borders: {
2020
buttonBorderRadiusMini: '8px',
21+
popoverBorderRadius: '12px',
2122
},
2223
} satisfies DeepPartial<MakeExtendable<Theme>>;
2324

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { createElement } from 'react';
2+
3+
import { type DomainPageHelpMenuConfig } from '../domain-page-help/domain-page-help.types';
4+
5+
export const mockDomainPageHelpMenuConfig = [
6+
{
7+
title: 'Documentation',
8+
items: [
9+
{
10+
kind: 'link',
11+
text: 'Get started (docs)',
12+
icon: () => createElement('span', {}, 'Docs Icon'),
13+
href: 'https://mock.docs.link',
14+
},
15+
],
16+
},
17+
{
18+
title: 'Commands',
19+
items: [
20+
{
21+
kind: 'modal',
22+
text: 'Domain commands',
23+
icon: () => createElement('span', {}, 'Cmds Icon'),
24+
modal: ({ isOpen }) =>
25+
createElement('div', {}, isOpen ? 'Open modal' : 'Closed modal'),
26+
},
27+
],
28+
},
29+
{
30+
title: 'Actions',
31+
items: [
32+
{
33+
kind: 'other',
34+
text: 'Custom action',
35+
icon: () => createElement('span', {}, 'Other Icon'),
36+
onClick: jest.fn(),
37+
},
38+
],
39+
},
40+
] as const satisfies DomainPageHelpMenuConfig;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { FaSlack } from 'react-icons/fa';
2+
import { MdArticle, MdTerminal } from 'react-icons/md';
3+
4+
import WorkflowPageCliCommandsModal from '@/views/workflow-page/workflow-page-cli-commands-modal/workflow-page-cli-commands-modal';
5+
6+
import { type DomainPageHelpMenuConfig } from '../domain-page-help/domain-page-help.types';
7+
8+
const domainPageHelpMenuConfig = [
9+
{
10+
title: 'Getting started',
11+
items: [
12+
{
13+
kind: 'link',
14+
text: 'Get started (docs)',
15+
icon: MdArticle,
16+
href: 'https://cadenceworkflow.io/docs/get-started',
17+
},
18+
],
19+
},
20+
{
21+
title: 'CLI Commands',
22+
items: [
23+
{
24+
kind: 'modal',
25+
text: 'Domain commands',
26+
icon: MdTerminal,
27+
modal: WorkflowPageCliCommandsModal,
28+
},
29+
],
30+
},
31+
{
32+
title: 'Other',
33+
items: [
34+
{
35+
kind: 'link',
36+
text: 'Cadence Docs',
37+
icon: MdArticle,
38+
href: 'https://cadenceworkflow.io/docs/concepts',
39+
},
40+
{
41+
kind: 'link',
42+
text: 'Cadence on Slack',
43+
icon: FaSlack,
44+
href: 'http://t.uber.com/cadence-slack',
45+
},
46+
],
47+
},
48+
] as const satisfies DomainPageHelpMenuConfig;
49+
50+
export default domainPageHelpMenuConfig;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { render, screen, userEvent } from '@/test-utils/rtl';
2+
3+
import { mockDomainPageHelpMenuConfig } from '../../__fixtures__/domain-page-help-menu-config';
4+
import DomainPageHelpItemButton from '../domain-page-help-item-button';
5+
6+
const linkItem = mockDomainPageHelpMenuConfig[0].items[0];
7+
const modalItem = mockDomainPageHelpMenuConfig[1].items[0];
8+
const otherItem = mockDomainPageHelpMenuConfig[2].items[0];
9+
10+
describe(DomainPageHelpItemButton.name, () => {
11+
beforeEach(() => {
12+
jest.clearAllMocks();
13+
});
14+
15+
it('renders a link button correctly', () => {
16+
render(<DomainPageHelpItemButton {...linkItem} />);
17+
18+
const linkItemButton = screen.getByRole('link');
19+
expect(linkItemButton).toBeInTheDocument();
20+
21+
expect(linkItemButton).toHaveTextContent('Get started (docs)');
22+
expect(linkItemButton).toHaveAttribute('target', '_blank');
23+
expect(linkItemButton).toHaveAttribute('rel', 'noreferrer');
24+
expect(linkItemButton).toHaveAttribute('href', 'https://mock.docs.link');
25+
26+
expect(screen.getByText('Docs Icon')).toBeInTheDocument();
27+
});
28+
29+
it('renders a modal button correctly and opens the modal on click', async () => {
30+
const user = userEvent.setup();
31+
render(<DomainPageHelpItemButton {...modalItem} />);
32+
33+
const buttonElement = screen.getByText('Domain commands');
34+
expect(buttonElement).toBeInTheDocument();
35+
36+
expect(screen.getByText('Cmds Icon')).toBeInTheDocument();
37+
38+
expect(screen.getByText('Closed modal')).toBeInTheDocument();
39+
40+
await user.click(buttonElement);
41+
42+
expect(await screen.findByText('Open modal')).toBeInTheDocument();
43+
});
44+
45+
it('renders an action button correctly and calls onClick handler', async () => {
46+
const user = userEvent.setup();
47+
render(<DomainPageHelpItemButton {...otherItem} />);
48+
49+
const button = screen.getByText('Custom action');
50+
expect(button).toBeInTheDocument();
51+
52+
expect(screen.getByText('Other Icon')).toBeInTheDocument();
53+
54+
await user.click(button);
55+
56+
expect(otherItem.onClick).toHaveBeenCalledTimes(1);
57+
});
58+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { type Theme, styled as createStyled } from 'baseui';
2+
import { type ButtonOverrides } from 'baseui/button';
3+
import type { StyleObject } from 'styletron-react';
4+
5+
export const overrides = {
6+
button: {
7+
BaseButton: {
8+
style: ({ $theme }: { $theme: Theme }): StyleObject => ({
9+
...$theme.typography.LabelSmall,
10+
justifyContent: 'flex-start',
11+
whiteSpace: 'nowrap',
12+
}),
13+
},
14+
} satisfies ButtonOverrides,
15+
};
16+
17+
export const styled = {
18+
ExternalLinkButtonContent: createStyled('div', ({ $theme }) => ({
19+
display: 'flex',
20+
gap: $theme.sizing.scale500,
21+
justifyContent: 'space-between',
22+
alignItems: 'center',
23+
flexGrow: 1,
24+
})),
25+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { useState } from 'react';
2+
3+
import { Button, type ButtonProps } from 'baseui/button';
4+
import Link from 'next/link';
5+
import { MdOpenInNew } from 'react-icons/md';
6+
7+
import { overrides, styled } from './domain-page-help-item-button.styles';
8+
import { type DomainPageHelpItem } from './domain-page-help-item-button.types';
9+
10+
export default function DomainPageHelpItemButton(item: DomainPageHelpItem) {
11+
const [isModalOpen, setIsModalOpen] = useState(false);
12+
13+
const baseButtonProps: ButtonProps = {
14+
kind: 'tertiary',
15+
size: 'compact',
16+
overrides: overrides.button,
17+
startEnhancer: item.icon,
18+
};
19+
20+
if (item.kind === 'link') {
21+
return (
22+
<Button
23+
{...baseButtonProps}
24+
$as={Link}
25+
target="_blank"
26+
rel="noreferrer"
27+
href={item.href}
28+
>
29+
<styled.ExternalLinkButtonContent>
30+
{item.text}
31+
<MdOpenInNew />
32+
</styled.ExternalLinkButtonContent>
33+
</Button>
34+
);
35+
}
36+
37+
if (item.kind === 'modal') {
38+
return (
39+
<>
40+
<Button {...baseButtonProps} onClick={() => setIsModalOpen(true)}>
41+
{item.text}
42+
</Button>
43+
<item.modal
44+
isOpen={isModalOpen}
45+
onClose={() => setIsModalOpen(false)}
46+
/>
47+
</>
48+
);
49+
}
50+
51+
return (
52+
<Button {...baseButtonProps} onClick={item.onClick}>
53+
{item.text}
54+
</Button>
55+
);
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { type IconProps } from 'baseui/icon';
2+
3+
interface DomainPageHelpBaseItem {
4+
kind: 'link' | 'modal' | 'other';
5+
text: string;
6+
icon: React.ComponentType<{
7+
size: IconProps['size'];
8+
color: IconProps['color'];
9+
}>;
10+
}
11+
12+
interface DomainPageHelpLinkItem extends DomainPageHelpBaseItem {
13+
kind: 'link';
14+
href: string;
15+
}
16+
17+
interface DomainPageHelpModalItem extends DomainPageHelpBaseItem {
18+
kind: 'modal';
19+
modal: React.ComponentType<{
20+
isOpen: boolean;
21+
onClose: () => void;
22+
}>;
23+
}
24+
25+
interface DomainPageHelpOtherItem extends DomainPageHelpBaseItem {
26+
kind: 'other';
27+
onClick: () => void;
28+
}
29+
30+
export type DomainPageHelpItem =
31+
| DomainPageHelpLinkItem
32+
| DomainPageHelpModalItem
33+
| DomainPageHelpOtherItem;

0 commit comments

Comments
 (0)