Skip to content

Commit 8d84540

Browse files
committed
feat: use tailwind for History component
1 parent b588ff6 commit 8d84540

File tree

12 files changed

+218
-163
lines changed

12 files changed

+218
-163
lines changed

src/components/App/App.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Console from 'components/Console';
44
import { useContext, useEffect } from 'react';
55
import { AppContext } from 'context/AppContext';
66
import { AppActions } from 'context/Reducer';
7+
import History from 'components/History';
78

89
const App: React.FC = () => {
910
const { dispatch } = useContext(AppContext);
@@ -33,6 +34,8 @@ const App: React.FC = () => {
3334

3435
<Console />
3536
</Split>
37+
38+
<History />
3639
</div>
3740
);
3841
};
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React from 'react';
2+
import { fireEvent, act, render, screen } from '@testing-library/react';
3+
import { AppContext } from 'context/AppContext';
4+
import { AppActions } from 'context/Reducer';
5+
import History from 'components/History';
6+
import * as StorageService from 'services/storage';
7+
8+
const getHistorySpy = jest.spyOn(StorageService, 'getHistory');
9+
const mockHistory = [
10+
{
11+
date: 'second',
12+
code: 'const a = 10',
13+
},
14+
{
15+
date: 'first',
16+
code: 'const b = 20',
17+
},
18+
];
19+
20+
describe('<History />', () => {
21+
const state = {
22+
historyOpen: true,
23+
} as AppState;
24+
const dispatch = jest.fn();
25+
26+
beforeEach(async () => {
27+
getHistorySpy.mockReturnValue([...mockHistory]);
28+
await act(async () => {
29+
render(
30+
<AppContext.Provider value={{ state, dispatch }}>
31+
<History />
32+
</AppContext.Provider>,
33+
);
34+
});
35+
});
36+
37+
afterEach(jest.clearAllMocks);
38+
39+
it('render history list', () => {
40+
const historyElement = screen.getByTestId('history-container');
41+
expect(historyElement.children.length).toEqual(2);
42+
});
43+
44+
it('calls closes the modal on button click', () => {
45+
const closeButton = screen.getByTestId('history-close-btn');
46+
fireEvent.click(closeButton);
47+
48+
expect(dispatch).toHaveBeenCalledWith({
49+
type: AppActions.HIDE_HISTORY,
50+
});
51+
});
52+
53+
it('restores history when restore button is clicked', async () => {
54+
// Expand
55+
const disclosureButtons = await screen.findAllByRole('button');
56+
for (const btn of disclosureButtons) {
57+
if (btn.textContent !== 'Restore') {
58+
await act(async () => {
59+
fireEvent.click(btn);
60+
});
61+
}
62+
}
63+
64+
const restoreButtons = await screen.findAllByRole('button', {
65+
name: /Restore/,
66+
});
67+
await act(async () => {
68+
fireEvent.click(restoreButtons[0]);
69+
});
70+
71+
expect(dispatch).toHaveBeenCalledWith({
72+
type: AppActions.LOAD_CODE_SAMPLE,
73+
payload: {
74+
codeSample: mockHistory[1].code,
75+
codeSampleName: '',
76+
},
77+
});
78+
});
79+
});

src/components/History/History.tsx

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { useContext } from 'react';
2+
import {
3+
Dialog,
4+
DialogPanel,
5+
DialogTitle,
6+
Disclosure,
7+
DisclosureButton,
8+
DisclosurePanel,
9+
} from '@headlessui/react';
10+
import { ChevronDownIcon } from '@heroicons/react/20/solid';
11+
import { XMarkIcon } from '@heroicons/react/24/outline';
12+
import { AppContext } from 'context/AppContext';
13+
import { AppActions } from 'context/Reducer';
14+
import { getHistory } from 'services/storage';
15+
16+
const History: React.FC = () => {
17+
const { state, dispatch } = useContext(AppContext);
18+
19+
const history = getHistory();
20+
21+
const makeKey = (item: HistoryItem, index: number) => `${item.date}_${index}`;
22+
23+
const handleRestore = (item: HistoryItem) => () => {
24+
const payload = {
25+
codeSample: item.code,
26+
codeSampleName: '',
27+
};
28+
29+
dispatch({ type: AppActions.LOAD_CODE_SAMPLE, payload });
30+
};
31+
32+
return (
33+
<Dialog
34+
open={state.historyOpen}
35+
onClose={() => dispatch({ type: AppActions.HIDE_HISTORY })}
36+
className="relative z-10"
37+
>
38+
<div className="fixed inset-0" />
39+
40+
<div className="fixed inset-0 overflow-hidden">
41+
<div className="absolute inset-0 overflow-hidden">
42+
<div className="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16">
43+
<DialogPanel
44+
transition
45+
className="pointer-events-auto w-screen max-w-2xl transform transition duration-500 ease-in-out data-closed:translate-x-full sm:duration-700"
46+
>
47+
<div className="flex h-full flex-col overflow-y-scroll bg-gray-900 text-white py-6 shadow-xl">
48+
<div className="px-4 sm:px-6">
49+
<div className="flex items-start justify-between">
50+
<DialogTitle className="text-base font-semibold text-white">
51+
Code History
52+
</DialogTitle>
53+
<div className="ml-3 flex h-7 items-center">
54+
<button
55+
data-testid="history-close-btn"
56+
type="button"
57+
onClick={() =>
58+
dispatch({ type: AppActions.HIDE_HISTORY })
59+
}
60+
className="relative rounded-md bg-gray-900 text-white hover:text-gray-100 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-hidden cursor-pointer"
61+
>
62+
<span className="absolute -inset-2.5" />
63+
<span className="sr-only">Close</span>
64+
<XMarkIcon aria-hidden="true" className="size-6" />
65+
</button>
66+
</div>
67+
</div>
68+
</div>
69+
<div className="relative mt-6 flex-1 px-4 sm:px-6">
70+
<div className="h-screen w-full">
71+
<div
72+
className="w-full divide-y divide-white/5 rounded-xl bg-white/5"
73+
data-testid="history-container"
74+
>
75+
{[...history].reverse().map((item, index) => {
76+
return (
77+
<Disclosure
78+
as="div"
79+
key={makeKey(item, index)}
80+
className="p-6"
81+
>
82+
<DisclosureButton className="group flex w-full items-center justify-between cursor-pointer">
83+
<span className="text-sm/6 font-medium text-white group-data-hover:text-white/80">
84+
({item.date} #({index + 1})){' - '}
85+
{item.code.slice(0, 50)}...
86+
</span>
87+
<ChevronDownIcon className="size-5 fill-white/60 group-data-hover:fill-white/50 group-data-open:rotate-180" />
88+
</DisclosureButton>
89+
<DisclosurePanel className="mt-2 text-sm/5 text-white/50">
90+
<pre
91+
className="
92+
bg-gray-800 text-green-200 rounded p-4 font-mono text-xs whitespace-pre-wrap break-words overflow-x-auto border border-gray-700 shadow-inner max-h-64"
93+
>
94+
{item.code}
95+
</pre>
96+
<div className="flex justify-end mt-4">
97+
<button
98+
type="button"
99+
className="bg-gray-900 text-green-200 rounded px-4 py-1 border border-gray-700 shadow hover:bg-gray-700 hover:text-green-100 focus:outline-none focus:ring-2 focus:ring-green-400 transition cursor-pointer"
100+
onClick={handleRestore(item)}
101+
>
102+
Restore
103+
</button>
104+
</div>
105+
</DisclosurePanel>
106+
</Disclosure>
107+
);
108+
})}
109+
</div>
110+
</div>
111+
</div>
112+
</div>
113+
</DialogPanel>
114+
</div>
115+
</div>
116+
</div>
117+
</Dialog>
118+
);
119+
};
120+
121+
export default History;

src/components/History/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from 'components/History/History';

src/components/HistoryModal/HistoryModal.spec.tsx

Lines changed: 0 additions & 77 deletions
This file was deleted.

src/components/HistoryModal/HistoryModal.tsx

Lines changed: 0 additions & 78 deletions
This file was deleted.

src/components/HistoryModal/index.tsx

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/components/Layout/ActionBar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const actionBarItems: ActionBarItem[] = [
3939
{
4040
label: 'History',
4141
icon: ClockIcon,
42-
payload: { type: AppActions.TOGGLE_HISTORY_MODAL },
42+
payload: { type: AppActions.SHOW_HISTORY },
4343
},
4444
{
4545
label: 'Code Samples',

src/context/AppContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const initialState: AppState = {
1111
error: '',
1212
loading: false,
1313
display: 'none',
14-
historyModalShown: false,
14+
historyOpen: false,
1515
};
1616

1717
export const AppContext = createContext<{

0 commit comments

Comments
 (0)