Skip to content
Merged
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
7 changes: 7 additions & 0 deletions global.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

/* Accessible focus styles for keyboard navigation */
.web-input-accessible:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
55 changes: 30 additions & 25 deletions src/app/call/[id]/edit.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ const WebInput: React.FC<WebInputProps> = ({ label, placeholder, value, onChange
const isDark = colorScheme === 'dark';

const inputStyles = StyleSheet.flatten([
styles.webInput,
webStyles.webInput as any,
isDark ? styles.webInputDark : styles.webInputLight,
error ? styles.webInputError : {},
disabled ? styles.webInputDisabled : {},
disabled ? (webStyles.webInputDisabled as any) : {},
multiline ? { minHeight: rows * 24 + 16 } : {},
]);

Expand Down Expand Up @@ -152,7 +152,7 @@ const WebSelect: React.FC<WebSelectProps> = ({ label, placeholder, value, onChan
{required ? <Text style={styles.required}> *</Text> : null}
</Text>
<select
style={StyleSheet.flatten([styles.webSelect, isDark ? styles.webSelectDark : styles.webSelectLight, error ? styles.webInputError : {}]) as React.CSSProperties}
style={StyleSheet.flatten([webStyles.webSelect as any, isDark ? styles.webSelectDark : styles.webSelectLight, error ? styles.webInputError : {}]) as React.CSSProperties}
value={value}
onChange={(e) => onChange(e.target.value)}
>
Expand Down Expand Up @@ -315,6 +315,7 @@ export default function EditCallWeb() {

window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showLocationPicker, showAddressSelection, showDispatchModal]);

const onSubmit = async (data: FormValues) => {
Expand Down Expand Up @@ -919,15 +920,6 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
},
webInput: {
width: '100%',
padding: 10,
paddingRight: 40,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
outline: 'none',
} as const,
webInputDark: {
backgroundColor: '#262626',
borderColor: '#404040',
Expand All @@ -941,23 +933,10 @@ const styles = StyleSheet.create({
webInputError: {
borderColor: '#ef4444',
},
webInputDisabled: {
opacity: 0.6,
cursor: 'not-allowed',
},
rightElement: {
position: 'absolute',
right: 8,
},
webSelect: {
width: '100%',
padding: 10,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
outline: 'none',
cursor: 'pointer',
},
webSelectDark: {
backgroundColor: '#262626',
borderColor: '#404040',
Expand Down Expand Up @@ -1211,3 +1190,29 @@ const styles = StyleSheet.create({
color: '#374151',
},
});

// Web-specific styles that use CSS-only properties
const webStyles: { [key: string]: React.CSSProperties } = {
webInput: {
width: '100%',
padding: 10,
paddingRight: 40,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
outline: 'none',
},
webInputDisabled: {
opacity: 0.6,
cursor: 'not-allowed',
},
webSelect: {
width: '100%',
padding: 10,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
outline: 'none',
cursor: 'pointer',
},
};
186 changes: 104 additions & 82 deletions src/app/call/new/index.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,21 @@
const isDark = colorScheme === 'dark';

const inputStyles = StyleSheet.flatten([
styles.webInput,
webStyles.webInput as any,
isDark ? styles.webInputDark : styles.webInputLight,
error ? styles.webInputError : {},
disabled ? styles.webInputDisabled : {},
disabled ? (webStyles.webInputDisabled as any) : {},
multiline ? { minHeight: rows * 24 + 16 } : {},
]);

// Add accessible focus styles for keyboard navigation
const accessibleInputStyles = {
...inputStyles,
outline: 'none',
} as React.CSSProperties & {
'&:focus-visible'?: React.CSSProperties;
};

return (
<View style={styles.webInputContainer}>
<Text style={StyleSheet.flatten([styles.webLabel, isDark ? styles.webLabelDark : styles.webLabelLight])}>
Expand All @@ -125,7 +133,8 @@
<View style={styles.inputWrapper}>
{multiline ? (
<textarea
style={inputStyles as React.CSSProperties}
className="web-input-accessible"
style={accessibleInputStyles as React.CSSProperties}
placeholder={placeholder}
value={value}
onChange={(e) => onChange(e.target.value)}
Expand All @@ -138,7 +147,8 @@
) : (
<input
type="text"
style={inputStyles as React.CSSProperties}
className="web-input-accessible"
style={accessibleInputStyles as React.CSSProperties}
placeholder={placeholder}
value={value}
onChange={(e) => onChange(e.target.value)}
Expand Down Expand Up @@ -171,17 +181,21 @@
const { colorScheme } = useColorScheme();
const isDark = colorScheme === 'dark';

const selectStyles = StyleSheet.flatten([webStyles.webSelect as any, isDark ? styles.webSelectDark : styles.webSelectLight, error ? styles.webInputError : {}]);

// Add accessible focus styles for keyboard navigation
const accessibleSelectStyles = {
...selectStyles,
outline: 'none',
} as React.CSSProperties;

return (
<View style={styles.webInputContainer}>
<Text style={StyleSheet.flatten([styles.webLabel, isDark ? styles.webLabelDark : styles.webLabelLight])}>
{label}
{required ? <Text style={styles.required}> *</Text> : null}
</Text>
<select
style={StyleSheet.flatten([styles.webSelect, isDark ? styles.webSelectDark : styles.webSelectLight, error ? styles.webInputError : {}]) as React.CSSProperties}
value={value}
onChange={(e) => onChange(e.target.value)}
>
<select className="web-input-accessible" style={accessibleSelectStyles} value={value} onChange={(e) => onChange(e.target.value)}>
<option value="">{placeholder}</option>
{options.map((option) => (
<option key={option.id} value={option.name}>
Expand Down Expand Up @@ -279,6 +293,61 @@
});
}, [trackEvent, callPriorities.length, callTypes.length]);

const onSubmit = useCallback(
async (data: FormValues) => {
try {
setIsSubmitting(true);

if (selectedLocation?.latitude && selectedLocation?.longitude) {
data.latitude = selectedLocation.latitude;
data.longitude = selectedLocation.longitude;
}

const priority = callPriorities.find((p) => p.Name === data.priority);
const type = callTypes.find((t) => t.Name === data.type);

if (!priority) {
setIsSubmitting(false);
toast.error(t('calls.invalid_priority'));
return;
}

if (!type) {
setIsSubmitting(false);
toast.error(t('calls.invalid_type'));
return;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

await createCall({
name: data.name,
nature: data.nature,
priority: priority.Id,
type: type.Id,
note: data.note,
address: data.address,
latitude: data.latitude,
longitude: data.longitude,
what3words: data.what3words,
plusCode: data.plusCode,
dispatchUsers: data.dispatchSelection?.users,
dispatchGroups: data.dispatchSelection?.groups,
dispatchRoles: data.dispatchSelection?.roles,
dispatchUnits: data.dispatchSelection?.units,
dispatchEveryone: data.dispatchSelection?.everyone,
});

toast.success(t('calls.create_success'));
router.push('/calls' as Href);
} catch (err) {
console.error('Error creating call:', err);
toast.error(t('calls.create_error'));
} finally {
setIsSubmitting(false);
}
},
[selectedLocation, callPriorities, callTypes, toast, t, router]

Check warning on line 348 in src/app/call/new/index.web.tsx

View workflow job for this annotation

GitHub Actions / test

React Hook useCallback has an unnecessary dependency: 'router'. Either exclude it or remove the dependency array. Outer scope values like 'router' aren't valid dependencies because mutating them doesn't re-render the component
);

// Keyboard shortcuts
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
Expand All @@ -303,57 +372,8 @@

window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [showLocationPicker, showAddressSelection, showDispatchModal, handleSubmit, onSubmit, router]);

const onSubmit = async (data: FormValues) => {
try {
setIsSubmitting(true);

if (selectedLocation?.latitude && selectedLocation?.longitude) {
data.latitude = selectedLocation.latitude;
data.longitude = selectedLocation.longitude;
}

const priority = callPriorities.find((p) => p.Name === data.priority);
const type = callTypes.find((t) => t.Name === data.type);

if (!priority) {
toast.error(t('calls.invalid_priority'));
return;
}

if (!type) {
toast.error(t('calls.invalid_type'));
return;
}

await createCall({
name: data.name,
nature: data.nature,
priority: priority.Id,
type: type.Id,
note: data.note,
address: data.address,
latitude: data.latitude,
longitude: data.longitude,
what3words: data.what3words,
plusCode: data.plusCode,
dispatchUsers: data.dispatchSelection?.users,
dispatchGroups: data.dispatchSelection?.groups,
dispatchRoles: data.dispatchSelection?.roles,
dispatchUnits: data.dispatchSelection?.units,
dispatchEveryone: data.dispatchSelection?.everyone,
});

toast.success(t('calls.create_success'));
router.push('/calls' as Href);
} catch (err) {
console.error('Error creating call:', err);
toast.error(t('calls.create_error'));
} finally {
setIsSubmitting(false);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showLocationPicker, showAddressSelection, showDispatchModal, handleSubmit, onSubmit]);

const handleLocationSelected = useCallback(
(location: { latitude: number; longitude: number; address?: string }) => {
Expand Down Expand Up @@ -1019,15 +1039,6 @@
flexDirection: 'row',
alignItems: 'center',
},
webInput: {
width: '100%',
padding: 10,
paddingRight: 40,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
outline: 'none',
} as const,
webInputDark: {
backgroundColor: '#262626',
borderColor: '#404040',
Expand All @@ -1041,23 +1052,10 @@
webInputError: {
borderColor: '#ef4444',
},
webInputDisabled: {
opacity: 0.6,
cursor: 'not-allowed',
},
rightElement: {
position: 'absolute',
right: 8,
},
webSelect: {
width: '100%',
padding: 10,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
outline: 'none',
cursor: 'pointer',
},
webSelectDark: {
backgroundColor: '#262626',
borderColor: '#404040',
Expand Down Expand Up @@ -1311,3 +1309,27 @@
color: '#374151',
},
});

// Web-specific styles that use CSS-only properties
const webStyles: { [key: string]: React.CSSProperties } = {
webInput: {
width: '100%',
padding: 10,
paddingRight: 40,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
},
webInputDisabled: {
opacity: 0.6,
cursor: 'not-allowed',
},
webSelect: {
width: '100%',
padding: 10,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
cursor: 'pointer',
},
};
Comment on lines +1313 to +1335
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid removing focus outline without an alternative.

outline: 'none' removes the default focus indicator, which can make keyboard navigation inaccessible. Prefer keeping the default outline unless you add a custom focus style.

🔧 Suggested change (restore default focus indicator)
  webInput: {
    width: '100%',
    padding: 10,
    paddingRight: 40,
    fontSize: 14,
    borderRadius: 8,
    borderWidth: 1,
-    outline: 'none',
  },
  webSelect: {
    width: '100%',
    padding: 10,
    fontSize: 14,
    borderRadius: 8,
    borderWidth: 1,
-    outline: 'none',
    cursor: 'pointer',
  },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Web-specific styles that use CSS-only properties
const webStyles: { [key: string]: React.CSSProperties } = {
webInput: {
width: '100%',
padding: 10,
paddingRight: 40,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
outline: 'none',
},
webInputDisabled: {
opacity: 0.6,
cursor: 'not-allowed',
},
webSelect: {
width: '100%',
padding: 10,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
outline: 'none',
cursor: 'pointer',
},
};
// Web-specific styles that use CSS-only properties
const webStyles: { [key: string]: React.CSSProperties } = {
webInput: {
width: '100%',
padding: 10,
paddingRight: 40,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
},
webInputDisabled: {
opacity: 0.6,
cursor: 'not-allowed',
},
webSelect: {
width: '100%',
padding: 10,
fontSize: 14,
borderRadius: 8,
borderWidth: 1,
cursor: 'pointer',
},
};
🤖 Prompt for AI Agents
In `@src/app/call/new/index.web.tsx` around lines 1294 - 1318, The styles in
webStyles remove the browser focus indicator (outline: 'none') which breaks
keyboard accessibility; remove outline: 'none' from webInput (and webSelect if
present) and replace it with an accessible custom focus style—either add new
style keys (e.g., webInputFocus, webSelectFocus) that set a visible
outline/boxShadow and outlineOffset or implement a :focus-visible rule in your
CSS and apply a className to inputs/selects so keyboard focus gets a clear
visible ring while preserving mouse appearance; update the components that use
webStyles.webInput/webSelect to apply the focus-class or focus style when
focused.