Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69f244b219 | ||
|
|
5b4c24e737 |
@@ -15,8 +15,6 @@ interface FormData {
|
|||||||
export function ContactForm() {
|
export function ContactForm() {
|
||||||
const t = useTranslations('pages.contact');
|
const t = useTranslations('pages.contact');
|
||||||
|
|
||||||
const MAX_MESSAGE_LENGTH = 1000;
|
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [submitStatus, setSubmitStatus] = useState<'idle' | 'success' | 'error'>('idle');
|
const [submitStatus, setSubmitStatus] = useState<'idle' | 'success' | 'error'>('idle');
|
||||||
const [errorMessage, setErrorMessage] = useState<string>('');
|
const [errorMessage, setErrorMessage] = useState<string>('');
|
||||||
@@ -28,8 +26,6 @@ export function ContactForm() {
|
|||||||
const [errors, setErrors] = useState<Partial<FormData>>({});
|
const [errors, setErrors] = useState<Partial<FormData>>({});
|
||||||
const [remainingAttempts, setRemainingAttempts] = useState<number | null>(null);
|
const [remainingAttempts, setRemainingAttempts] = useState<number | null>(null);
|
||||||
|
|
||||||
const messageLength = formData.message.length;
|
|
||||||
|
|
||||||
const validateForm = () => {
|
const validateForm = () => {
|
||||||
const newErrors: Partial<FormData> = {};
|
const newErrors: Partial<FormData> = {};
|
||||||
|
|
||||||
@@ -323,7 +319,6 @@ export function ContactForm() {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder={t('contactForm.message.placeholder')}
|
placeholder={t('contactForm.message.placeholder')}
|
||||||
rows={6}
|
rows={6}
|
||||||
maxLength={MAX_MESSAGE_LENGTH}
|
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
aria-required="true"
|
aria-required="true"
|
||||||
aria-invalid={!!errors.message}
|
aria-invalid={!!errors.message}
|
||||||
@@ -333,19 +328,6 @@ export function ContactForm() {
|
|||||||
{errors.message && (
|
{errors.message && (
|
||||||
<p id="message-error" role="alert" className="text-sm text-red-400">{errors.message}</p>
|
<p id="message-error" role="alert" className="text-sm text-red-400">{errors.message}</p>
|
||||||
)}
|
)}
|
||||||
<div className="flex justify-end">
|
|
||||||
<p
|
|
||||||
className={`text-xs transition-colors ${
|
|
||||||
messageLength > MAX_MESSAGE_LENGTH
|
|
||||||
? 'text-red-500'
|
|
||||||
: messageLength >= MAX_MESSAGE_LENGTH * 0.8
|
|
||||||
? 'text-yellow-500'
|
|
||||||
: 'text-zinc-500'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{t('contactForm.characterCounter', { current: messageLength, max: MAX_MESSAGE_LENGTH })}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const LanguageSwitcher = () => {
|
|||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isMobile, setIsMobile] = useState(false);
|
const [isMobile, setIsMobile] = useState(false);
|
||||||
|
const [focusedIndex, setFocusedIndex] = useState<number>(-1);
|
||||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const languages = [
|
const languages = [
|
||||||
@@ -30,8 +31,28 @@ const LanguageSwitcher = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||||
if (e.key === 'Escape') {
|
if (!isOpen) return;
|
||||||
|
|
||||||
|
switch (e.key) {
|
||||||
|
case 'Escape':
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
|
setFocusedIndex(-1);
|
||||||
|
break;
|
||||||
|
case 'ArrowDown':
|
||||||
|
e.preventDefault();
|
||||||
|
setFocusedIndex(prev => (prev + 1) % languages.length);
|
||||||
|
break;
|
||||||
|
case 'ArrowUp':
|
||||||
|
e.preventDefault();
|
||||||
|
setFocusedIndex(prev => (prev - 1 + languages.length) % languages.length);
|
||||||
|
break;
|
||||||
|
case 'Enter':
|
||||||
|
e.preventDefault();
|
||||||
|
if (focusedIndex >= 0) {
|
||||||
|
handleLanguageChange(languages[focusedIndex].code);
|
||||||
|
setFocusedIndex(-1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,7 +84,10 @@ const LanguageSwitcher = () => {
|
|||||||
px-4 py-2 rounded-full transition-all duration-200
|
px-4 py-2 rounded-full transition-all duration-200
|
||||||
hover:bg-zinc-800/50 border border-transparent hover:border-zinc-800
|
hover:bg-zinc-800/50 border border-transparent hover:border-zinc-800
|
||||||
hover:scale-105 active:scale-95"
|
hover:scale-105 active:scale-95"
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
onClick={() => {
|
||||||
|
setIsOpen(!isOpen);
|
||||||
|
setFocusedIndex(-1);
|
||||||
|
}}
|
||||||
aria-expanded={isOpen}
|
aria-expanded={isOpen}
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
aria-label="Select language"
|
aria-label="Select language"
|
||||||
@@ -79,18 +103,21 @@ const LanguageSwitcher = () => {
|
|||||||
${isOpen ? 'opacity-100 scale-100' : 'opacity-0 scale-95 pointer-events-none'}`}
|
${isOpen ? 'opacity-100 scale-100' : 'opacity-0 scale-95 pointer-events-none'}`}
|
||||||
role="listbox"
|
role="listbox"
|
||||||
aria-label="Languages"
|
aria-label="Languages"
|
||||||
|
aria-activedescendant={focusedIndex >= 0 ? `lang-option-${languages[focusedIndex].code}` : undefined}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
>
|
>
|
||||||
<div className="py-1">
|
<div className="py-1">
|
||||||
{languages.map((lang) => (
|
{languages.map((lang, index) => (
|
||||||
<button
|
<button
|
||||||
key={lang.code}
|
key={lang.code}
|
||||||
|
id={`lang-option-${lang.code}`}
|
||||||
className={`w-full text-left px-4 py-2 text-sm transition-colors duration-200
|
className={`w-full text-left px-4 py-2 text-sm transition-colors duration-200
|
||||||
hover:bg-zinc-800/50
|
hover:bg-zinc-800/50
|
||||||
${locale === lang.code
|
${locale === lang.code
|
||||||
? 'bg-zinc-800 text-white'
|
? 'bg-zinc-800 text-white'
|
||||||
: 'text-zinc-400 hover:text-white'
|
: 'text-zinc-400 hover:text-white'
|
||||||
}`}
|
}
|
||||||
|
${focusedIndex === index ? 'ring-2 ring-orange-500 ring-inset' : ''}`}
|
||||||
onClick={() => handleLanguageChange(lang.code)}
|
onClick={() => handleLanguageChange(lang.code)}
|
||||||
role="option"
|
role="option"
|
||||||
aria-selected={locale === lang.code}
|
aria-selected={locale === lang.code}
|
||||||
|
|||||||
@@ -394,7 +394,6 @@
|
|||||||
"label": "Nachricht",
|
"label": "Nachricht",
|
||||||
"placeholder": "Ihre Nachricht an mich..."
|
"placeholder": "Ihre Nachricht an mich..."
|
||||||
},
|
},
|
||||||
"characterCounter": "{current}/{max} Zeichen",
|
|
||||||
"submit": "Nachricht senden",
|
"submit": "Nachricht senden",
|
||||||
"submitting": "Nachricht wird gesendet...",
|
"submitting": "Nachricht wird gesendet...",
|
||||||
"successMessage": "Vielen Dank für Ihre Nachricht! Ich werde mich so schnell wie möglich bei Ihnen melden.",
|
"successMessage": "Vielen Dank für Ihre Nachricht! Ich werde mich so schnell wie möglich bei Ihnen melden.",
|
||||||
|
|||||||
@@ -409,7 +409,6 @@
|
|||||||
"label": "Message",
|
"label": "Message",
|
||||||
"placeholder": "Your message to me..."
|
"placeholder": "Your message to me..."
|
||||||
},
|
},
|
||||||
"characterCounter": "{current}/{max} characters",
|
|
||||||
"submit": "Send Message",
|
"submit": "Send Message",
|
||||||
"submitting": "Sending message...",
|
"submitting": "Sending message...",
|
||||||
"successMessage": "Thank you for your message! I will get back to you as soon as possible.",
|
"successMessage": "Thank you for your message! I will get back to you as soon as possible.",
|
||||||
|
|||||||
@@ -416,7 +416,6 @@
|
|||||||
"label": "Poruka",
|
"label": "Poruka",
|
||||||
"placeholder": "Vasa poruka za mene..."
|
"placeholder": "Vasa poruka za mene..."
|
||||||
},
|
},
|
||||||
"characterCounter": "{current}/{max} karaktera",
|
|
||||||
"submit": "Posalji poruku",
|
"submit": "Posalji poruku",
|
||||||
"submitting": "Slanje poruke...",
|
"submitting": "Slanje poruke...",
|
||||||
"successMessage": "Hvala vam na poruci! Javicu vam se sto je pre moguce.",
|
"successMessage": "Hvala vam na poruci! Javicu vam se sto je pre moguce.",
|
||||||
|
|||||||
Reference in New Issue
Block a user