diff --git a/.eslintrc.json b/.eslintrc.json
index 895e356..a2864e2 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -103,4 +103,4 @@
}
]
}
-}
\ No newline at end of file
+}
diff --git a/__TESTS__/sorting.spec.tsx b/__TESTS__/sorting.spec.tsx
new file mode 100644
index 0000000..c8d1b1c
--- /dev/null
+++ b/__TESTS__/sorting.spec.tsx
@@ -0,0 +1,99 @@
+/* eslint-disable import/no-extraneous-dependencies */
+import { fireEvent, render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+/* eslint-enable import/no-extraneous-dependencies */
+import React from 'react';
+
+import Sorting from '../pages/sorting';
+
+describe('Sorting', () => {
+ it('should sort', () => {
+ render();
+
+ const CommaInputs = [
+ 'c,a,b',
+ 'c,a,b',
+ 'cc,a,bbb',
+ 'cc,a,bbb',
+ 'c,a,b',
+ ];
+ const otherSeparatorsInputs = [
+ 'c a b',
+ 'c.a.b',
+ 'c;a;b',
+ 'c\na\nb',
+ ];
+ const CommaOutputs = [
+ 'a,b,c',
+ 'c,b,a',
+ 'a,cc,bbb',
+ 'bbb,cc,a',
+ 'b,a,c',
+ ];
+ const otherSeparatorOutputs = [
+ 'a b c',
+ 'a.b.c',
+ 'a;b;c',
+ 'a\nb\nc',
+ ];
+
+ const SortStyleList = [
+ 'ascending',
+ 'descending',
+ 'ascending2',
+ 'descending2',
+ 'reverse',
+ ];
+
+ const SortButton = screen.getByRole('button', {
+ name: 'Sort',
+ }) as HTMLButtonElement;
+
+ CommaInputs.forEach((input, index) => {
+ const inputText = screen.getByLabelText(
+ 'Input',
+ ) as HTMLInputElement;
+ userEvent.type(inputText, input).then(() => {
+ const sortStyle = screen.getByLabelText(
+ 'Sort Style',
+ ) as HTMLInputElement;
+ fireEvent.change(sortStyle, {
+ target: { value: SortStyleList[index] },
+ });
+ userEvent.click(SortButton).then(() => {
+ const outputText = screen.getByLabelText('Output');
+ expect(outputText.innerText).toBe(CommaOutputs[index]);
+ });
+ });
+ });
+
+ const SeparatorsList = ['space', 'dot', 'semicolon', 'lineBreak'];
+ otherSeparatorsInputs.forEach((input, index) => {
+ const inputText = screen.getByLabelText(
+ 'Input',
+ ) as HTMLInputElement;
+ userEvent.type(inputText, input).then(() => {
+ const separator = screen.getByLabelText(
+ 'Separator',
+ ) as HTMLInputElement;
+ fireEvent.change(separator, {
+ target: { value: SeparatorsList[index] },
+ });
+
+ const sortStyle = screen.getByLabelText(
+ 'Sort Style',
+ ) as HTMLInputElement;
+ fireEvent.change(sortStyle, {
+ target: { value: SortStyleList[index] },
+ });
+
+ userEvent.click(SortButton).then(() => {
+ const outputText = screen.getByLabelText('Output');
+ expect(outputText.innerText).toBe(
+ otherSeparatorOutputs[index],
+ );
+ });
+ });
+ });
+ });
+});
diff --git a/data/nav.ts b/data/nav.ts
index df2c2ca..aa43dec 100644
--- a/data/nav.ts
+++ b/data/nav.ts
@@ -7,6 +7,7 @@ import Fingerprint from '@mui/icons-material/Fingerprint';
import HomeIcon from '@mui/icons-material/Home';
import LinkIcon from '@mui/icons-material/Link';
import Looks6 from '@mui/icons-material/Looks6';
+import SortByAlphaIcon from '@mui/icons-material/SortByAlpha';
// Icons Index: https://mui.com/material-ui/material-icons/
@@ -46,6 +47,11 @@ const navItems = [
href: '/url-encode',
Icon: LinkIcon,
},
+ {
+ title: 'Sorting',
+ href: '/sorting',
+ Icon: SortByAlphaIcon,
+ },
{
title: 'HTML',
href: '/html',
diff --git a/pages/sorting.tsx b/pages/sorting.tsx
new file mode 100644
index 0000000..31511ff
--- /dev/null
+++ b/pages/sorting.tsx
@@ -0,0 +1,251 @@
+import ClearIcon from '@mui/icons-material/Clear';
+import ContentCopyIcon from '@mui/icons-material/ContentCopy';
+import ContentPasteGoIcon from '@mui/icons-material/ContentPasteGo';
+import { Typography } from '@mui/material';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import FormControl from '@mui/material/FormControl';
+import InputLabel from '@mui/material/InputLabel';
+import MenuItem from '@mui/material/MenuItem';
+import Select, { SelectChangeEvent } from '@mui/material/Select';
+import TextField from '@mui/material/TextField';
+import React, { useState } from 'react';
+
+import Heading from '../components/Heading';
+import Layout from '../components/Layout';
+import Toast, { ToastProps } from '../components/Toast';
+import useLocalState from '../hooks/useLocalState';
+import useSupportsClipboardRead from '../hooks/useSupportsClipboardRead';
+
+export default function Sorting() {
+ const supportsClipboardRead = useSupportsClipboardRead();
+ const [inputText, setInputText] = useLocalState({
+ key: 'inputTextLocalState',
+ defaultValue: '',
+ });
+
+ const [sortStyle, setSortStyle] = useLocalState({
+ key: 'sortStyleLocalState',
+ defaultValue: 'ascending',
+ });
+
+ const [separator, setSeparator] = useLocalState({
+ key: 'separatorLocalState',
+ defaultValue: ',',
+ });
+
+ const [outputText, setOutputText] = useLocalState({
+ key: 'outputTextLocalState',
+ defaultValue: '',
+ });
+
+ const [toastOpen, setToastOpen] = useState(false);
+ const [toastMessage, setToastMessage] = useState('');
+ const [toastSeverity, setToastSeverity] =
+ useState('success');
+
+ const handleSortStyleChange = (event: SelectChangeEvent) => {
+ setSortStyle(event.target.value);
+ };
+
+ const handleSeparatorChange = (event: SelectChangeEvent) => {
+ setSeparator(event.target.value);
+ };
+
+ const handleClear = () => {
+ setInputText('');
+ setOutputText('');
+ };
+
+ const handlePaste = async () => {
+ const textCopied = await navigator.clipboard.readText();
+ setInputText(textCopied);
+ setOutputText(decodeURIComponent(textCopied));
+ };
+
+ const handleSort = () => {
+ if (inputText === '') {
+ setToastMessage('Please enter some text to sort.');
+ setToastSeverity('error');
+ setToastOpen(true);
+ return;
+ }
+
+ if (separator === '') {
+ setToastMessage('Please enter a separator.');
+ setToastSeverity('error');
+ setToastOpen(true);
+ return;
+ }
+
+ let inputTextArray;
+
+ if (separator === 'newLine') {
+ inputTextArray = inputText.split(/\r\n|\n\r|\n|\r/);
+ } else {
+ inputTextArray = inputText.split(separator);
+ }
+ const outputTextArray =
+ sortStyle === 'reverse'
+ ? inputTextArray.reverse()
+ : inputTextArray.sort((a, b) => {
+ if (sortStyle === 'ascending') {
+ return a.localeCompare(b);
+ }
+ if (sortStyle === 'descending') {
+ return b.localeCompare(a);
+ }
+ if (sortStyle === 'ascending2') {
+ if (a.length === b.length) {
+ return a.localeCompare(b);
+ }
+ return a.length - b.length;
+ }
+ if (sortStyle === 'descending2') {
+ if (a.length === b.length) {
+ return b.localeCompare(a);
+ }
+ return b.length - a.length;
+ }
+ return 0;
+ });
+
+ if (separator === 'newLine')
+ setOutputText(outputTextArray.join('\n'));
+ else setOutputText(outputTextArray.join(separator));
+ };
+
+ return (
+
+ Sorting Tool
+
+ Paste or Type an list and select options from the dropdown to
+ sort
+
+
+
+
+ setInputText(event.target.value)}
+ multiline
+ />
+
+ }
+ onClick={handleClear}
+ >
+ Clear
+
+ {supportsClipboardRead && (
+ }
+ onClick={handlePaste}
+ >
+ Paste
+
+ )}
+
+
+ Sort Style
+
+
+
+
+ Separator
+
+
+
+
+
+
+
+
+
+
+ }
+ disabled={!outputText}
+ onClick={() => {
+ navigator.clipboard.writeText(outputText || '').then(
+ () => {
+ setToastMessage('Copied to clipboard');
+ setToastSeverity('success');
+ setToastOpen(true);
+ },
+ () => {
+ setToastMessage('Failed to copy to clipboard');
+ setToastSeverity('error');
+ setToastOpen(true);
+ },
+ );
+ }}
+ >
+ Copy
+
+
+
+
+ setToastOpen(false)}
+ />
+
+ );
+}