Skip to content

Commit f5c5160

Browse files
docs: Add modern Mobile UX and Accessibility FocusTrap POCs for 2026 roadmap
1 parent 10c0b59 commit f5c5160

3 files changed

Lines changed: 144 additions & 0 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useEffect, useRef } from 'react';
2+
3+
/**
4+
* POC: GSoC 2026 - Keyboard Navigation & WCAG Compliance
5+
* Implements a strict Focus Trap ref to prevent users from tabbing out
6+
* of critical UI layers like EmojiPickers or Modals.
7+
*/
8+
export const useFocusTrap = (isActive) => {
9+
const containerRef = useRef(null);
10+
11+
useEffect(() => {
12+
if (!isActive) return;
13+
14+
const handleKeyDown = (event) => {
15+
if (event.key !== 'Tab') return;
16+
17+
const focusableElements = containerRef.current.querySelectorAll(
18+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
19+
);
20+
const firstElement = focusableElements[0];
21+
const lastElement = focusableElements[focusableElements.length - 1];
22+
23+
if (event.shiftKey) {
24+
if (document.activeElement === firstElement) {
25+
lastElement.focus();
26+
event.preventDefault();
27+
}
28+
} else {
29+
if (document.activeElement === lastElement) {
30+
firstElement.focus();
31+
event.preventDefault();
32+
}
33+
}
34+
};
35+
36+
window.addEventListener('keydown', handleKeyDown);
37+
return () => window.removeEventListener('keydown', handleKeyDown);
38+
}, [isActive]);
39+
40+
return containerRef;
41+
};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { useState, useEffect } from 'react';
2+
3+
/**
4+
* POC: GSoC 2026 - Modern Mobile Viewport & Touch Target Management
5+
* Demonstrates technical depth in handling the "Dynamic Viewport Height" (dvh)
6+
* and responsive state management for mobile-first UI refactors.
7+
*/
8+
export const useResponsive = () => {
9+
const [isMobile, setIsMobile] = useState(false);
10+
const [vh, setVh] = useState('100vh');
11+
12+
useEffect(() => {
13+
const handleResize = () => {
14+
// Logic for modern dvh (dynamic viewport height) fallback
15+
const currentVh = window.innerHeight * 0.01;
16+
document.documentElement.style.setProperty('--vh', `${currentVh}px`);
17+
setVh(`${window.innerHeight}px`);
18+
setIsMobile(window.innerWidth < 768);
19+
};
20+
21+
window.addEventListener('resize', handleResize);
22+
handleResize();
23+
24+
return () => window.removeEventListener('resize', handleResize);
25+
}, []);
26+
27+
return { isMobile, vh };
28+
};
29+
30+
/**
31+
* MobileTouchTarget: Ensures all clickable nodes meet the 44x44 safe hit-area
32+
* requirement for WCAG 2.1 and mobile platform guidelines.
33+
*/
34+
export const MobileTouchTarget = ({ children, style = {} }) => (
35+
<div
36+
style={{
37+
minHeight: '44px',
38+
minWidth: '44px',
39+
display: 'inline-flex',
40+
alignItems: 'center',
41+
justifyContent: 'center',
42+
...style,
43+
}}
44+
>
45+
{children}
46+
</div>
47+
);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React, { useState } from 'react';
2+
import { useFocusTrap } from '../lib/a11y/useFocusTrap';
3+
import { MobileTouchTarget, useResponsive } from '../lib/mobile/useResponsive';
4+
import { ThemeProvider, Box, ActionButton } from '@embeddedchat/ui-elements';
5+
import DefaultTheme from '../theme/DefaultTheme';
6+
import GlobalStyles from '../views/GlobalStyles';
7+
8+
export default {
9+
title: 'EmbeddedChat/POC: UX & Accessibility',
10+
};
11+
12+
export const MobileUX_and_FocusTrap = () => {
13+
const [isModalOpen, setIsModalOpen] = useState(false);
14+
const { isMobile, vh } = useResponsive();
15+
const trapRef = useFocusTrap(isModalOpen);
16+
17+
return (
18+
<ThemeProvider theme={DefaultTheme}>
19+
<GlobalStyles />
20+
<div style={{ padding: '2rem', background: '#f9f9f9', minHeight: '100vh' }}>
21+
<h3>Mobile UX & Accessibility POC</h3>
22+
<p>Current Viewport Status: <strong>{isMobile ? 'MOBILE' : 'DESKTOP'}</strong></p>
23+
<p>Calculated Dynamic Height: {vh}</p>
24+
25+
<Box style={{ marginTop: '1rem', border: '1px solid #ddd', padding: '1rem', borderRadius: '8px' }}>
26+
<h4>Touch Area Optimization (Section 4.4)</h4>
27+
<p>This button is wrapped in a 44x44 safe zone for mobile users:</p>
28+
<MobileTouchTarget style={{ background: '#eee', border: '1px dashed #666' }}>
29+
<ActionButton icon="edit" />
30+
</MobileTouchTarget>
31+
</Box>
32+
33+
<Box style={{ marginTop: '2rem' }}>
34+
<h4>Keyboard Focus Trap (Section 4.5)</h4>
35+
<button onClick={() => setIsModalOpen(true)}>Open Accessibile Modal POC</button>
36+
37+
{isModalOpen && (
38+
<div
39+
ref={trapRef}
40+
style={{
41+
position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
42+
background: 'white', padding: '2rem', border: '2px solid black', zIndex: 1000
43+
}}
44+
>
45+
<h5>Focus is trapped here!</h5>
46+
<p>Try tabbing—your cursor will only stay between these fields:</p>
47+
<input type="text" placeholder="First focusable item" /><br /><br />
48+
<button>Second item</button><br /><br />
49+
<button onClick={() => setIsModalOpen(false)}>Close (Exit Trap)</button>
50+
</div>
51+
)}
52+
</Box>
53+
</div>
54+
</ThemeProvider>
55+
);
56+
};

0 commit comments

Comments
 (0)