Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* Accessibility Focus Styles */
button:focus-visible,
textarea:focus-visible,
input:focus-visible {
outline: 3px solid #ffa500;
outline-offset: 2px;
}

/* Button hover and active states */
button:hover {
background-color: #006a8e !important;
transform: translateY(-1px);
}

button:active {
transform: translateY(0);
}

/* High contrast mode support */
@media (prefers-contrast: high) {
button,
textarea {
border-width: 3px;
}
}

/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
button,
* {
transition: none !important;
}
}
117 changes: 100 additions & 17 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,129 @@
import { useState } from 'react';
import { add } from './stringCalculator';
import './App.css';

const App = () => {
const [input, setInput] = useState('');
const [result] = useState(null);
const [result, setResult] = useState<number | null>(null);
const [error, setError] = useState<string>('');

const handleCalculate = () => {};
const handleCalculate = () => {
try {
setError('');
const sum = add(input);
setResult(sum);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
setResult(null);
}
};

const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setInput(e.target.value);
setError('');
setResult(null);
};

return (
<div style={{ padding: '20px', backgroundColor: '#fff', color: '#aaa' }}>
<div style={{ padding: '20px', maxWidth: '800px', margin: '0 auto', backgroundColor: '#fff', color: '#333' }}>
<img
src='https://images.unsplash.com/photo-1594352161389-11756265d1b5?q=80&w=2574&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D'
width={600}
height={400}
alt='Calculator with colorful number buttons and mathematical symbols'
style={{ display: 'block', maxWidth: '100%', height: 'auto', marginBottom: '20px' }}
/>

<h2>String Calculator</h2>
<h1 style={{ fontSize: '32px', marginBottom: '10px', color: '#333' }}>String Calculator</h1>

<h1 style={{ fontSize: '20px' }}>Enter numbers</h1>
<p style={{ fontSize: '18px', marginBottom: '20px', color: '#555' }}>
Enter numbers below to calculate their sum
</p>

<label
htmlFor='numbers-input'
style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold', fontSize: '16px', color: '#333' }}
>
Numbers to calculate
</label>
<textarea
style={{ margin: '10px 0', color: '#aaa' }}
placeholder='Enter numbers'
id='numbers-input'
style={{
margin: '0',
color: '#333',
padding: '10px',
width: '100%',
maxWidth: '600px',
minHeight: '100px',
border: '2px solid #333',
borderRadius: '4px',
fontSize: '16px',
fontFamily: 'inherit',
boxSizing: 'border-box'
}}
placeholder='Enter numbers (e.g., 1,2,3 or use custom delimiters)'
value={input}
onChange={(e) => setInput(e.target.value)}
onChange={handleInputChange}
aria-describedby='input-help'
/>
<p id='input-help' style={{ fontSize: '14px', color: '#555', marginTop: '8px', marginBottom: '15px' }}>
You can use commas, newlines, or custom delimiters (e.g., //;\n1;2)
</p>

<div
<button
onClick={handleCalculate}
style={{
padding: '10px',
padding: '12px 24px',
backgroundColor: '#008cba',
color: '#fff',
border: 'none',
}}>
border: '2px solid #006a8e',
borderRadius: '4px',
fontSize: '16px',
cursor: 'pointer',
fontWeight: 'bold',
marginTop: '10px',
transition: 'background-color 0.2s ease'
}}
aria-label='Calculate the sum of entered numbers'
>
Calculate
</div>
</button>

{result !== null && <p style={{ color: 'green' }}>Result: {result}</p>}
{result !== null && (
<div
role='status'
aria-live='polite'
style={{
color: '#006400',
fontSize: '20px',
fontWeight: 'bold',
marginTop: '20px',
padding: '15px',
backgroundColor: '#d4edda',
border: '2px solid #006400',
borderRadius: '4px'
}}
>
<strong>Result:</strong> {result}
</div>
)}

<div role='alert'>
<p>Make sure you enter numbers correctly!</p>
</div>
{error && (
<div
role='alert'
aria-live='assertive'
style={{
color: '#d8000c',
backgroundColor: '#ffd2d2',
padding: '15px',
marginTop: '20px',
border: '2px solid #d8000c',
borderRadius: '4px'
}}
>
<strong>Error:</strong> {error}
</div>
)}
</div>
);
};
Expand Down
55 changes: 55 additions & 0 deletions src/stringCalculator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { describe, it, expect } from 'vitest';
import { add } from './stringCalculator';

describe('String Calculator', () => {
describe('add', () => {
it('should return 0 for an empty string', () => {
expect(add('')).toBe(0);
});

it('should return the number itself for a single number', () => {
expect(add('1')).toBe(1);
expect(add('5')).toBe(5);
});

it('should return the sum of two comma-separated numbers', () => {
expect(add('1,2')).toBe(3);
expect(add('10,20')).toBe(30);
});

it('should handle multiple numbers', () => {
expect(add('1,2,3,4,5')).toBe(15);
});

it('should handle newlines as delimiters', () => {
expect(add('1\n2,3')).toBe(6);
});

it('should support custom delimiters', () => {
expect(add('//;\n1;2')).toBe(3);
expect(add('//|\n1|2|3')).toBe(6);
});

it('should throw an error for negative numbers', () => {
expect(() => add('1,-2,3')).toThrow('Negative numbers not allowed: -2');
expect(() => add('1,-2,-3')).toThrow('Negative numbers not allowed: -2, -3');
});

it('should ignore numbers greater than 1000', () => {
expect(add('2,1001')).toBe(2);
expect(add('1000,1001,2')).toBe(1002);
});

it('should handle delimiters of any length', () => {
expect(add('//[***]\n1***2***3')).toBe(6);
});

it('should handle multiple delimiters', () => {
expect(add('//[*][%]\n1*2%3')).toBe(6);
});

it('should handle multiple delimiters of any length', () => {
expect(add('//[**][%%]\n1**2%%3')).toBe(6);
});
});
});
48 changes: 48 additions & 0 deletions src/stringCalculator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export function add(numbers: string): number {
if (numbers === '') {
return 0;
}

let delimiter = /[,\n]/;
let numberString = numbers;

// Check for custom delimiter
if (numbers.startsWith('//')) {
const delimiterEndIndex = numbers.indexOf('\n');
const delimiterSection = numbers.substring(2, delimiterEndIndex);
numberString = numbers.substring(delimiterEndIndex + 1);

// Handle multiple delimiters of any length
if (delimiterSection.startsWith('[')) {
const delimiters = delimiterSection.match(/\[([^\]]+)\]/g);
if (delimiters) {
const escapedDelimiters = delimiters
.map(d => d.slice(1, -1))
.map(d => d.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
.join('|');
delimiter = new RegExp(escapedDelimiters);
}
} else {
// Single character delimiter
const escapedDelimiter = delimiterSection.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
delimiter = new RegExp(escapedDelimiter);
}
}

const nums = numberString
.split(delimiter)
.map(n => n.trim())
.filter(n => n !== '')
.map(Number);

// Check for negative numbers
const negatives = nums.filter(n => n < 0);
if (negatives.length > 0) {
throw new Error(`Negative numbers not allowed: ${negatives.join(', ')}`);
}

// Sum numbers, ignoring those greater than 1000
return nums
.filter(n => n <= 1000)
.reduce((sum, n) => sum + n, 0);
}