Skip to content

Commit

Permalink
Share button (#126)
Browse files Browse the repository at this point in the history
* new ShareBtn component

* add ShareBtn to control center

* share button calls function to set the current URL, copy to the clipboard, and alert the user
  • Loading branch information
dpgraham4401 authored May 10, 2024
1 parent 662f06e commit 42ea6e4
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 8 deletions.
21 changes: 15 additions & 6 deletions src/components/Tree/ControlCenter/ControlCenter.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import '@testing-library/jest-dom';
import { cleanup, render, screen } from '@testing-library/react';
import { cleanup, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ControlCenter } from 'components/Tree/ControlCenter/index';
import { ReactFlowProvider } from 'reactflow';
import { TreeDirection } from 'store';
import { renderWithProviders } from 'test-utils';
import { afterEach, describe, expect, test, vi } from 'vitest';

interface TestComponentProps {
Expand Down Expand Up @@ -36,35 +37,43 @@ afterEach(() => cleanup());

describe('ControlCenter', () => {
test('renders', () => {
render(<TestComponent />);
renderWithProviders(<TestComponent />);
expect(screen.getByTestId('controlCenter')).toBeInTheDocument();
});
test('renders a map toggle button', () => {
render(<TestComponent />);
renderWithProviders(<TestComponent />);
expect(screen.getByRole('button', { name: /minimap/i })).toBeInTheDocument();
});
test('toggles the minimap visibility', async () => {
const user = userEvent.setup();
const setMapVisible = vi.fn();
const { rerender } = render(<TestComponent mapVisible={true} setMapVisible={setMapVisible} />);
const { rerender } = renderWithProviders(
<TestComponent mapVisible={true} setMapVisible={setMapVisible} />
);
await user.click(screen.getByRole('button', { name: /minimap/i }));
expect(setMapVisible).toHaveBeenCalled();
rerender(<TestComponent setMapVisible={setMapVisible} />);
await user.click(screen.getByRole('button', { name: /minimap/i }));
expect(setMapVisible).toHaveBeenCalled();
});
test('renders a layout toggle button', () => {
render(<TestComponent />);
renderWithProviders(<TestComponent />);
expect(screen.getByRole('button', { name: /layout/i })).toBeInTheDocument();
});
test('toggles the layout direction', async () => {
const user = userEvent.setup();
const setDirection = vi.fn();
const { rerender } = render(<TestComponent setDirection={setDirection} direction={'LR'} />);
const { rerender } = renderWithProviders(
<TestComponent setDirection={setDirection} direction={'LR'} />
);
await user.click(screen.getByRole('button', { name: /layout/i }));
expect(setDirection).toHaveBeenCalled();
rerender(<TestComponent setDirection={setDirection} />);
await user.click(screen.getByRole('button', { name: /layout/i }));
expect(setDirection).toHaveBeenCalled();
});
test('renders a share button', () => {
renderWithProviders(<TestComponent />);
expect(screen.getByRole('button', { name: /share/i })).toBeInTheDocument();
});
});
2 changes: 2 additions & 0 deletions src/components/Tree/ControlCenter/ControlCenter.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { LayoutBtn } from 'components/Tree/ControlCenter/Controls/LayoutBtn/LayoutBtn';
import { MiniMapBtn } from 'components/Tree/ControlCenter/Controls/MiniMapBtn/MiniMapBtn';
import { ShareBtn } from 'components/Tree/ControlCenter/Controls/ShareBtn/ShareBtn';
import { Controls } from 'reactflow';
import { TreeDirection } from 'store';

Expand Down Expand Up @@ -29,6 +30,7 @@ export const ControlCenter = ({
return (
<div data-testid="controlCenter">
<Controls showInteractive={false}>
<ShareBtn />
<MiniMapBtn visible={mapVisible} onClick={toggleMap} />
<LayoutBtn isHorizontal={isHorizontal} toggleDirection={toggleDirection} />
</Controls>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import '@testing-library/jest-dom';
import { screen } from '@testing-library/react';
import { ShareBtn } from 'components/Tree/ControlCenter/Controls/ShareBtn/ShareBtn';
import { renderWithProviders } from 'test-utils';
import { describe, expect, test } from 'vitest';

describe('ShareBtn', () => {
test('render a button with aria label "share" ', () => {
renderWithProviders(<ShareBtn />);
expect(screen.getByRole('button', { name: /share/i })).toBeInTheDocument();
});
});
14 changes: 14 additions & 0 deletions src/components/Tree/ControlCenter/Controls/ShareBtn/ShareBtn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useUrl } from 'hooks/useUrl/useUrl';
import { LuShare } from 'react-icons/lu';
import { ControlButton } from 'reactflow';

/** Button to generate a shareable URL .*/
export const ShareBtn = () => {
const { copyTreeUrlToClipboard } = useUrl();

return (
<ControlButton aria-label="share diagram" onClick={copyTreeUrlToClipboard}>
<LuShare className="font-bold" />
</ControlButton>
);
};
17 changes: 16 additions & 1 deletion src/hooks/useUrl/useUrl.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useDecisions } from 'hooks/useDecisions/useDecisions';
import { useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useLocation, useSearchParams } from 'react-router-dom';

export interface UsesPathReturn {
pathParam: string;
setPathParam: (pathId: string) => void;
pathname: string;
copyTreeUrlToClipboard: () => void;
}

/**
Expand All @@ -12,15 +15,27 @@ export interface UsesPathReturn {
export const useUrl = () => {
const [urlQueryParams, setUrlQueryParams] = useSearchParams();
const [pathParam, setPathParam] = useState<string | null | undefined>(urlQueryParams.get('path'));
const { pathname } = useLocation();
const { path } = useDecisions();

const setUrlPathId = (pathId: string) => {
urlQueryParams.set('path', pathId);
setUrlQueryParams(urlQueryParams);
setPathParam(pathId);
};

const copyUrl = () => {
if (path.length < 1) return;
setUrlPathId(path[path.length - 1].selected);
navigator.clipboard
.writeText(window.location.href)
.then(() => window.alert('URL copied to clipboard'));
};

return {
pathParam,
setPathParam: setUrlPathId,
pathname,
copyTreeUrlToClipboard: copyUrl,
} as UsesPathReturn;
};
2 changes: 1 addition & 1 deletion src/store/TreeSlice/treeSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export type TreeDirection = 'TB' | 'LR';

export interface Decision {
nodeId: string;
selected: string | boolean;
selected: string;
}

export type DecisionPath = Array<Decision>;
Expand Down

0 comments on commit 42ea6e4

Please sign in to comment.