Skip to content

Conversation

@jbdevprimary
Copy link
Contributor

@jbdevprimary jbdevprimary commented Jan 18, 2026

Summary

Expands the ThumbCode component library with 25+ production-ready UI components across 5 categories, providing a comprehensive foundation for mobile app development.

Components Added

Layout Components (4):

  • Container - Responsive wrapper with safe area support
  • Stack/HStack/VStack - Flexible layout primitives
  • Spacer - Flexible spacing component
  • Divider - Visual separator with organic styling

Form Components (6):

  • Select - Dropdown with modal picker
  • Checkbox - Checkbox with label/description support
  • Switch - Animated toggle switch
  • TextArea - Multi-line text input with character count
  • Radio/RadioGroup - Radio button group
  • FormField - Form field wrapper with validation display

Feedback Components (8):

  • Modal/ConfirmDialog - Dialog overlays with organic styling
  • Toast - Notification system
  • Loading/Spinner/Skeleton/LoadingOverlay - Loading states
  • ProgressBar/ProgressCircle/StepsProgress - Progress indicators
  • BottomSheet/ActionSheet - Bottom modal sheets

Display Components (7):

  • Avatar/AvatarGroup - User avatars with stacking
  • Badge/StatusBadge - Labels and status indicators
  • EmptyState/ErrorState/NoResults - Empty state displays
  • Tooltip/InfoTip - Contextual information popups

Code Components (2):

  • DiffViewer - Code diff display with syntax highlighting
  • FileTree - File/folder hierarchy navigation

Design Compliance

All components follow ThumbCode's brand guidelines:

  • ✅ Organic "daube" border-radius styling
  • ✅ Coral/Teal/Gold color palette
  • ✅ Fraunces/Cabin/JetBrains Mono typography
  • ✅ Dark mode optimized
  • ✅ Accessibility attributes (accessibilityRole, accessibilityState)
  • ✅ Full TypeScript types

Technical Details

  • 27 new files, ~2,750 lines of code
  • All components use NativeWind (Tailwind) for styling
  • Animated components use React Native's Animated API
  • Consistent prop patterns across all components

Closes #13

Test Plan

  • TypeScript type checking passes (pnpm tsc --noEmit)
  • Lint passes (pnpm lint) - 1 complexity warning only
  • All 129 existing tests pass (npx jest)
  • Visual testing in Expo app
  • Accessibility testing with screen reader

🤖 Generated with Claude Code

Summary by CodeRabbit

New Features

  • Added comprehensive UI component library with support for code visualization (DiffViewer, FileTree)
  • Added display components: Avatar, Badge, EmptyState, and Tooltip for common visual patterns
  • Added form controls: Checkbox, RadioGroup, Select, Switch, and TextArea with accessibility support
  • Added feedback components: BottomSheet, ActionSheet, Modal, ConfirmDialog, Progress indicators, Toast notifications, and Loading states
  • Added layout primitives: Container, Divider, Spacer, and Stack (HStack/VStack) for flexible view composition

✏️ Tip: You can customize this high-level summary in your review settings.

Add 25+ production-ready UI components across 5 categories:

**Layout Components:**
- Container: Responsive wrapper with safe area support
- Stack/HStack/VStack: Flexible layout primitives
- Spacer: Flexible spacing component
- Divider: Visual separator

**Form Components:**
- Select: Dropdown with modal picker
- Checkbox: Checkbox with label/description
- Switch: Animated toggle switch
- TextArea: Multi-line text input
- Radio: RadioGroup component
- FormField: Form field wrapper with validation

**Feedback Components:**
- Modal/ConfirmDialog: Dialog overlays
- Toast: Notification system
- Loading/Spinner/Skeleton: Loading states
- Progress: ProgressBar, ProgressCircle, StepsProgress
- BottomSheet/ActionSheet: Bottom modals

**Display Components:**
- Avatar/AvatarGroup: User avatars
- Badge/StatusBadge: Labels and indicators
- EmptyState/ErrorState/NoResults: Empty states
- Tooltip/InfoTip: Contextual information

**Code Components:**
- DiffViewer: Code diff display
- FileTree: File hierarchy navigation

All components follow ThumbCode's organic "daube" styling with
proper TypeScript types and accessibility attributes.

Closes #13

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@jbdevprimary
Copy link
Contributor Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 18, 2026

Warning

Rate limit exceeded

@jbdevprimary has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 34 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between c7c91f5 and da0b08c.

📒 Files selected for processing (27)
  • src/components/code/DiffViewer.tsx
  • src/components/code/FileTree.tsx
  • src/components/code/index.ts
  • src/components/display/Avatar.tsx
  • src/components/display/Badge.tsx
  • src/components/display/EmptyState.tsx
  • src/components/display/Tooltip.tsx
  • src/components/display/index.ts
  • src/components/feedback/BottomSheet.tsx
  • src/components/feedback/Loading.tsx
  • src/components/feedback/Modal.tsx
  • src/components/feedback/Progress.tsx
  • src/components/feedback/Toast.tsx
  • src/components/feedback/index.ts
  • src/components/form/Checkbox.tsx
  • src/components/form/FormField.tsx
  • src/components/form/Radio.tsx
  • src/components/form/Select.tsx
  • src/components/form/Switch.tsx
  • src/components/form/TextArea.tsx
  • src/components/form/index.ts
  • src/components/index.ts
  • src/components/layout/Container.tsx
  • src/components/layout/Divider.tsx
  • src/components/layout/Spacer.tsx
  • src/components/layout/Stack.tsx
  • src/components/layout/index.ts
📝 Walkthrough

Walkthrough

This pull request significantly expands a React Native UI component library from 5 basic components to a comprehensive production-ready set across seven categories: code components (DiffViewer, FileTree), display components (Avatar, Badge, EmptyState, Tooltip), feedback components (BottomSheet, Modal, Toast, Loading, Progress), form components (Checkbox, RadioGroup, Select, Switch, TextArea, FormField), and layout components (Container, Stack, Divider, Spacer). Each file introduces new functional components with TypeScript prop interfaces and internal styling/animation logic. Barrel export files consolidate public APIs at module and root levels.

Changes

Cohort / File(s) Summary
Code Display Components
src/components/code/DiffViewer.tsx, src/components/code/FileTree.tsx, src/components/code/index.ts
New diff viewer with line-by-line rendering (+181 lines), expandable file tree with status indicators and icon mapping (+203 lines), and barrel export consolidating both.
Display: Avatar & Badge
src/components/display/Avatar.tsx, src/components/display/Badge.tsx
Avatar component with image/initials fallback, sizes, status indicators (+147 lines); Badge with variants and StatusBadge composition (+102 lines).
Display: Empty States & Tooltips
src/components/display/EmptyState.tsx, src/components/display/Tooltip.tsx, src/components/display/index.ts
EmptyState, ErrorState, NoResults variants (+137 lines); Tooltip with positioned reveal and InfoTip wrapper using long-press (+128 lines); display index re-exports.
Feedback: Bottom & Modal Sheets
src/components/feedback/BottomSheet.tsx, src/components/feedback/Modal.tsx
BottomSheet with pan-gesture drag-to-dismiss and height presets, plus ActionSheet composition (+225 lines); Modal with size variants and ConfirmDialog composition (+164 lines).
Feedback: Loading & Progress
src/components/feedback/Loading.tsx, src/components/feedback/Progress.tsx, src/components/feedback/Toast.tsx
Spinner, Skeleton, LoadingOverlay components (+143 lines); ProgressBar, ProgressCircle, StepsProgress indicators (+239 lines); Toast with auto-dismiss and slide animation (+163 lines); feedback index.
Form Input Components
src/components/form/Checkbox.tsx, src/components/form/FormField.tsx, src/components/form/Radio.tsx, src/components/form/Select.tsx, src/components/form/Switch.tsx, src/components/form/TextArea.tsx, src/components/form/index.ts
Checkbox (+78 lines), FormField label/error wrapper (+61 lines), RadioGroup vertical/horizontal (+106 lines), Select dropdown with modal (+132 lines), Switch toggle with spring animation (+91 lines), TextArea multi-line with character count (+105 lines), form index.
Layout Primitives
src/components/layout/Container.tsx, src/components/layout/Divider.tsx, src/components/layout/Spacer.tsx, src/components/layout/Stack.tsx, src/components/layout/index.ts
Container with responsive padding/max-width (+83 lines), Divider horizontal/vertical (+59 lines), Spacer flex/fixed sizing (+39 lines), Stack/HStack/VStack flex layouts with spacing/alignment (+96 lines), layout index.
Root Index
src/components/index.ts
Central barrel export aggregating all subsection exports (chat, code, display, feedback, form, layout, ui).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 Hop, hop, the component burrow grows!
DiffViewers and Tooltips in tidy rows,
Modals and Toasts pop up with delight,
Forms and layouts—the library's quite bright!
From humble beginnings, now sturdy and grand,
A UI feast across the React Native land! 🌟

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The PR delivers 25+ components across Layout, Form, Feedback, Display, and Code categories, addressing most core requirements from #13 [Layout, Form, Feedback, Display, Code]. However, Navigation components (TabBar, NavigationBar, Breadcrumbs, DrawerMenu) are not implemented, and Storybook, comprehensive tests (>90% coverage), and full documentation are outstanding. Verify completion of Navigation components, Storybook, test coverage goals, and comprehensive documentation before final merge.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: expanding the component library to a production-ready set of components.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #13 requirements to expand the component library with Layout, Form, Feedback, Display, and Code components. No out-of-scope changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 18, 2026

✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🤖 Fix all issues with AI agents
In `@src/components/code/DiffViewer.tsx`:
- Around line 66-73: The diff routine in DiffViewer (the block manipulating
result, oldLines, newLines, oldIndex, newIndex) only emits a 'remove' when
oldLines[oldIndex] !== newLines[newIndex], dropping the new line; fix by
emitting both the removal and the corresponding addition when lines differ (push
a {type: 'remove', ...} for oldLines[oldIndex] and a {type: 'add', ...} for
newLines[newIndex]) and advance both oldIndex and newIndex accordingly (or
implement a simple lookahead to detect pure insertions/removals); update the
logic inside the function that builds result (e.g., computeDiff / the loop that
uses oldIndex/newIndex) so new lines are not lost.
- Line 129: The inline style on the View in DiffViewer.tsx uses an invalid CSS
string for borderRadius; replace style={{ borderRadius: '12px 10px 14px 8px' }}
with numeric React Native corner props instead—use borderTopLeftRadius: 12,
borderTopRightRadius: 10, borderBottomRightRadius: 14, and
borderBottomLeftRadius: 8 (numbers, no "px") so the View's rounded corners
render correctly.

In `@src/components/code/FileTree.tsx`:
- Around line 187-201: The View in the FileTree component is using an invalid
string for borderRadius; replace the string `'12px 10px 14px 8px'` with numeric
React Native style properties (e.g. borderTopLeftRadius, borderTopRightRadius,
borderBottomRightRadius, borderBottomLeftRadius) on the same View where
sortedData.map renders FileTreeNode so the radii are numbers (no "px") and match
the intended asymmetry.

In `@src/components/display/Badge.tsx`:
- Around line 63-71: The Badge component's View sets style={{ borderRadius: '6px
8px 6px 10px' }}, which is invalid in React Native; change the style to use
numeric values and explicit corner properties (e.g. borderTopLeftRadius,
borderTopRightRadius, borderBottomRightRadius, borderBottomLeftRadius) or a
single numeric borderRadius value in the View returned by Badge so the radii are
numbers instead of a CSS string.

In `@src/components/display/EmptyState.tsx`:
- Around line 70-86: The Pressable instances in EmptyState use an invalid string
borderRadius style ("8px 10px 8px 12px"); update the style on the Pressable(s)
that render secondaryAction and action to use React Native-compatible numeric
borderRadius values (e.g., borderRadius: 8) or implement per-corner numeric
properties (borderTopLeftRadius, borderTopRightRadius, borderBottomRightRadius,
borderBottomLeftRadius) to approximate the intended shape; adjust the
secondaryAction and action Pressable styles accordingly and remove the string
value so RN will honor the radius.

In `@src/components/display/Tooltip.tsx`:
- Around line 94-99: The borderRadius style on the Animated.View is using an
invalid CSS string ('8px 10px 8px 12px'); replace it with React Native numeric
corner properties (e.g. borderTopLeftRadius, borderTopRightRadius,
borderBottomRightRadius, borderBottomLeftRadius) using numbers (without 'px')
and merge them into the style array (the same place where borderRadius is
currently added), ensuring types line up with getTooltipStyle() and
Animated.View’s style prop.
- Around line 53-85: getTooltipStyle currently uses percentage strings and
numeric translate values (e.g., left: '50%' with transform: [{ translateX: -50
}]) which don't yield proper centering in React Native; modify the tooltip to
measure its own size via onLayout (capture measured width/height in state) and
update getTooltipStyle to compute numeric centering offsets: replace left:
'50%'/top: '50%' and translateX/Y -50 with numeric left/top values based on
layout.x/layout.y plus half of the target element, and use transform: [{
translateX: -measuredWidth/2 }] or translateY: -measuredHeight/2 as appropriate
in the getTooltipStyle function and the Animated.View that renders the tooltip
(referencing getTooltipStyle, position, layout and the new
measuredWidth/measuredHeight state).

In `@src/components/feedback/BottomSheet.tsx`:
- Around line 214-221: The inline style on the cancel Pressable uses an invalid
string for borderRadius ('12px 14px 12px 16px'); update the style in
BottomSheet.tsx (the Pressable that renders cancel button) to use numeric radius
values or explicit corner properties (e.g. replace style: { borderRadius: '12px
14px 12px 16px' } with an object like { borderTopLeftRadius: 12,
borderTopRightRadius: 14, borderBottomRightRadius: 12, borderBottomLeftRadius:
16 } or a single numeric borderRadius without 'px') so it conforms to React
Native style types.
- Around line 70-92: PanResponder created in the useRef (panResponder /
PanResponder.create) captures the initial onClose closure and becomes stale if
onClose changes; fix by storing the latest onClose in a mutable ref (e.g.,
onCloseRef.current) updated inside the component on each render and replace
direct calls to onClose inside onPanResponderRelease with onCloseRef.current();
ensure the ref is declared with useRef and updated before the PanResponder usage
so translateY and gesture logic remain unchanged.

In `@src/components/feedback/Loading.tsx`:
- Around line 90-105: The useEffect in Loading.tsx starts an Animated.loop but
never stops it on unmount; modify the effect that creates the loop (the
Animated.loop call using pulseAnim and Animated.timing) to capture the returned
animation instance (e.g., const animation = Animated.loop(...)), call
animation.start(), and return a cleanup function that stops the loop
(animation.stop()) so the pulseAnim loop is torn down when the component
unmounts; apply the same pattern used to fix Skeleton if present.
- Around line 132-142: The overlay uses an invalid string borderRadius and lacks
accessibility/interaction blocking; in the Loading component replace the string
style with numeric corner radii (e.g., borderTopLeftRadius: 16,
borderTopRightRadius: 14, borderBottomLeftRadius: 18, borderBottomRightRadius:
12) for the inner View, and make the outer overlay accessible and modal by
adding accessible={true}, accessibilityViewIsModal={true}, an accessibilityLabel
like message ?? 'Loading', and accessibilityLiveRegion="polite" (or
accessibilityRole="alert") so screen readers announce the loading state; ensure
the overlay blocks underlying interaction by keeping it covering the screen
(pointerEvents can remain default/"auto" on the outer View) and set
accessibilityElementsHidden={false} on the overlay and true on any background
content if needed.
- Around line 34-43: The effect creates an infinite Animated.loop with
Animated.timing and never stops it; update the useEffect that references
spinValue to capture the returned animation (const animation =
Animated.loop(...)), call animation.start(), and return a cleanup function that
calls animation.stop() (and optionally reset spinValue with
spinValue.setValue(0)) so the animation is properly stopped on component
unmount; locate this in the useEffect that creates the Animated.loop and uses
spinValue.

In `@src/components/feedback/Modal.tsx`:
- Around line 68-73: The inline style in Modal.tsx is using an invalid CSS
string for borderRadius ("20px 16px 20px 18px") which React Native doesn't
accept; update the style in the component (the style prop where isFull is used)
to use numeric radius properties: set borderRadius to 0 when isFull is true,
otherwise remove the string and instead set numeric values like
borderTopLeftRadius, borderTopRightRadius, borderBottomRightRadius, and
borderBottomLeftRadius to the corresponding numbers (e.g., 20, 16, 20, 18) so
React Native receives numeric values.
- Around line 141-157: The Pressable buttons in Modal.tsx use an invalid string
value for style.borderRadius ('8px 10px 8px 12px') which React Native doesn't
accept; update both Pressable elements (the cancel and confirm buttons) to use
numeric border radius properties instead — either a single numeric borderRadius
(e.g., 8) or explicit side properties like borderTopLeftRadius,
borderTopRightRadius, borderBottomRightRadius, borderBottomLeftRadius with
numeric values; apply the change to the style prop on both Pressable instances
(and consider moving these styles into a StyleSheet or shared constant for
consistency).

In `@src/components/form/Checkbox.tsx`:
- Around line 53-57: The inline style in the Checkbox component uses an invalid
CSS-style borderRadius string; update the style in Checkbox (where style={{
width: styles.box, height: styles.box, borderRadius: '4px 6px 4px 6px' }}) to
use React Native numeric corner properties instead—replace borderRadius with
borderTopLeftRadius, borderTopRightRadius, borderBottomRightRadius, and
borderBottomLeftRadius set to the appropriate numeric values (e.g., 4 and 6) so
the component accepts the radii correctly (same approach used to fix
Tooltip.tsx).

In `@src/components/form/Select.tsx`:
- Around line 57-72: The Pressable used as the select trigger is missing
accessibility attributes and uses invalid string borderRadius values; update the
Pressable (and related props like setIsOpen/disabled/hasError/selectedOption) to
include accessibilityRole="button" (or "combobox" if your platform supports it),
accessibilityState={{ disabled, expanded: isOpen }}, and an accessibilityLabel
(e.g., selectedOption?.label || placeholder) so screen readers know purpose and
state, and replace the string borderRadius with numeric style properties
(borderTopLeftRadius, borderTopRightRadius, borderBottomLeftRadius,
borderBottomRightRadius) or a single numeric borderRadius value to fix the React
Native style error.

In `@src/components/form/TextArea.tsx`:
- Around line 74-80: The style object in the TextArea component is setting
borderRadius to an invalid CSS string; replace that string with numeric
per-corner React Native props (e.g., borderTopLeftRadius, borderTopRightRadius,
borderBottomRightRadius, borderBottomLeftRadius) using numbers instead of a
string and keep the other properties (minHeight, maxHeight, textAlignVertical,
lineHeight) unchanged so the style object is valid for React Native.
🟡 Minor comments (13)
src/components/code/FileTree.tsx-112-119 (1)

112-119: Status color may override selection color due to class order.

When a file is both selected and has a status (e.g., modified), the statusColor class is appended after the selection color class. Depending on NativeWind's handling, this could cause the status color to override the selection highlight.

Consider using separate Text elements or conditional logic to ensure both indicators are visible.

src/components/display/Avatar.tsx-44-51 (1)

44-51: Potential runtime error if name contains empty segments.

If name contains consecutive spaces (e.g., "John Doe"), splitting produces empty strings, and accessing part[0] on an empty string returns undefined, which would be rendered.

🐛 Proposed fix
 function getInitials(name: string): string {
   return name
     .split(' ')
+    .filter(Boolean)
     .map((part) => part[0])
     .join('')
     .toUpperCase()
     .slice(0, 2);
 }
src/components/layout/Spacer.tsx-29-38 (1)

29-38: Edge case: size={0} will be ignored due to falsy check.

When size is 0, the expression size ? ... : undefined evaluates to undefined because 0 is falsy in JavaScript. This causes the component to use flex spacing instead of a zero-dimension spacer.

If zero-spacing is a valid use case, consider using size !== undefined instead.

🐛 Proposed fix
 export function Spacer({ size, flex = 1, direction = 'vertical' }: SpacerProps) {
-  const dimension = size ? (typeof size === 'number' ? size : sizeValues[size]) : undefined;
+  const dimension = size !== undefined ? (typeof size === 'number' ? size : sizeValues[size]) : undefined;

   const style: ViewStyle = {
-    flex: size ? undefined : flex,
+    flex: size !== undefined ? undefined : flex,
     width: direction === 'horizontal' ? dimension : undefined,
     height: direction === 'vertical' ? dimension : undefined,
   };
src/components/display/Avatar.tsx-112-129 (1)

112-129: Weak key generation may cause React reconciliation issues.

Using avatar.name || index as a key can lead to collisions when multiple avatars share the same name or are unnamed. This could cause incorrect rendering or state retention.

Consider requiring a unique id field on avatar items, or use a combination of properties.

♻️ Suggested interface update
 interface AvatarGroupProps {
   /** List of avatars */
-  avatars: Array<{ src?: string; name?: string }>;
+  avatars: Array<{ id: string; src?: string; name?: string }>;
   /** Maximum avatars to show before +N */
   max?: number;
   /** Size of avatars */
   size?: AvatarSize;
 }

 // Then in the map:
-key={`${avatar.name || index}`}
+key={avatar.id}
src/components/display/Badge.tsx-50-61 (1)

50-61: Fragile string manipulation for dot background color.

The .replace('/20', '') approach assumes the background class always contains /20. If a variant without opacity is added (e.g., bg-neutral-600), this replacement would have no effect, and the dot would retain the semi-transparent background.

Consider storing the solid color separately in variantStyles or using a more robust approach.

♻️ Suggested improvement
-const variantStyles: Record<BadgeVariant, { bg: string; text: string }> = {
-  default: { bg: 'bg-neutral-600', text: 'text-neutral-200' },
-  primary: { bg: 'bg-coral-500/20', text: 'text-coral-400' },
+const variantStyles: Record<BadgeVariant, { bg: string; bgSolid: string; text: string }> = {
+  default: { bg: 'bg-neutral-600', bgSolid: 'bg-neutral-600', text: 'text-neutral-200' },
+  primary: { bg: 'bg-coral-500/20', bgSolid: 'bg-coral-500', text: 'text-coral-400' },
   // ... other variants
 };

 // Then in dot rendering:
-className={colors.bg.replace('/20', '')}
+className={colors.bgSolid}
src/components/feedback/BottomSheet.tsx-139-141 (1)

139-141: Same showsVerticalScrollIndicator prop issue as Modal.

When scrollable is false, ContentWrapper is a View which doesn't accept this prop.

Use the same fix pattern as suggested for Modal.tsx - conditionally render ScrollView or View instead of dynamic component assignment.

src/components/feedback/Modal.tsx-52-53 (1)

52-53: showsVerticalScrollIndicator passed to View when not scrollable.

When scrollable is false, ContentWrapper is a View, which doesn't accept showsVerticalScrollIndicator. This will cause a React Native warning about unrecognized props.

Proposed fix
-  const ContentWrapper = scrollable ? ScrollView : View;
+  const contentProps = scrollable
+    ? { showsVerticalScrollIndicator: false }
+    : {};

   // ...

-          <ContentWrapper className="px-5 py-4" showsVerticalScrollIndicator={false}>
+          {scrollable ? (
+            <ScrollView className="px-5 py-4" showsVerticalScrollIndicator={false}>
+              {children}
+            </ScrollView>
+          ) : (
+            <View className="px-5 py-4">{children}</View>
+          )}
-            {children}
-          </ContentWrapper>

Also applies to: 86-86

src/components/feedback/BottomSheet.tsx-22-22 (1)

22-22: SCREEN_HEIGHT won't update on device rotation.

The screen height is captured once at module load time. If the device rotates, SCREEN_HEIGHT will be stale, causing incorrect height calculations for 'half' and 'full' presets.

Suggested fix using useWindowDimensions
-import {
-  Animated,
-  Dimensions,
-  ...
-} from 'react-native';
+import {
+  Animated,
+  useWindowDimensions,
+  ...
+} from 'react-native';

-const { height: SCREEN_HEIGHT } = Dimensions.get('window');

 export function BottomSheet({...}: BottomSheetProps) {
+  const { height: screenHeight } = useWindowDimensions();
   // ... use screenHeight instead of SCREEN_HEIGHT
src/components/form/Select.tsx-82-84 (1)

82-84: stopPropagation() may not reliably prevent backdrop dismiss in React Native.

Unlike web, React Native's event system doesn't use DOM event bubbling. e.stopPropagation() on a nested Pressable may not prevent the parent's onPress from firing in all cases. The current structure where the outer Pressable wraps content can lead to accidental dismissals.

Suggested alternative using onStartShouldSetResponder
-        <Pressable onPress={(e) => e.stopPropagation()}>
+        <View onStartShouldSetResponder={() => true}>
           <View
             className="bg-surface m-4 max-h-80 overflow-hidden"
-            style={{ borderRadius: '20px 16px 20px 18px' }}
+            style={{ borderRadius: 18 }}
           >

Alternatively, restructure so the backdrop and content are siblings rather than parent-child.

src/components/code/DiffViewer.tsx-131-147 (1)

131-147: Add accessibility attributes to the collapsible header.

The collapsible header Pressable is missing accessibility attributes, which impacts screen reader users. Per PR objectives, accessibility support is a requirement.

♿ Proposed accessibility improvements
       <Pressable
         onPress={() => setCollapsed(!collapsed)}
         className="flex-row items-center justify-between px-3 py-2 bg-neutral-800 border-b border-neutral-700"
+        accessibilityRole="button"
+        accessibilityLabel={`${filename || 'Diff'}: ${additions} additions, ${deletions} deletions. ${collapsed ? 'Expand' : 'Collapse'}`}
+        accessibilityState={{ expanded: !collapsed }}
       >
src/components/display/Tooltip.tsx-29-51 (1)

29-51: Missing cleanup for timeout on component unmount.

If the component unmounts while the tooltip delay timer is pending, setTimeout will still fire and attempt to update state on an unmounted component. Add cleanup in a useEffect.

🐛 Add cleanup effect
  const timeoutRef = useRef<ReturnType<typeof setTimeout>>();

+ useEffect(() => {
+   return () => {
+     if (timeoutRef.current) {
+       clearTimeout(timeoutRef.current);
+     }
+   };
+ }, []);

  const showTooltip = () => {
src/components/feedback/Progress.tsx-123-171 (1)

123-171: ProgressCircle animation is a no-op (rotateAnim unused).

rotateAnim updates but isn’t applied, so the indicator jumps instead of animating. Bind the animated value to rotation.

✅ Suggested fix
   useEffect(() => {
     Animated.timing(rotateAnim, {
       toValue: clampedValue,
       duration: 500,
       useNativeDriver: true,
     }).start();
   }, [clampedValue, rotateAnim]);

+  const rotate = rotateAnim.interpolate({
+    inputRange: [0, 100],
+    outputRange: ['0deg', '360deg'],
+  });
...
-          <View
+          <Animated.View
             style={{
               width: size,
               height: size,
               borderRadius: size / 2,
               borderWidth: strokeWidth,
               borderColor: circleColors[color],
               borderLeftColor: 'transparent',
               borderBottomColor: 'transparent',
-              transform: [{ rotate: `${(clampedValue / 100) * 360}deg` }],
+              transform: [{ rotate }],
             }}
           />
src/components/feedback/Progress.tsx-203-209 (1)

203-209: Use per-corner radii; React Native doesn't support multi-value borderRadius shorthand.

The borderRadius value with multiple space-separated values (like '8px 10px 8px 12px') is invalid in React Native and will be ignored. Use individual corner radius properties instead.

✅ Suggested fix
               <View
                 className={`w-8 h-8 items-center justify-center ${
                   isCompleted ? 'bg-teal-600' : isCurrent ? 'bg-coral-500' : 'bg-neutral-700'
                 }`}
-                style={{ borderRadius: '8px 10px 8px 12px' }}
+                style={{
+                  borderTopLeftRadius: 8,
+                  borderTopRightRadius: 10,
+                  borderBottomRightRadius: 8,
+                  borderBottomLeftRadius: 12,
+                }}
               >
🧹 Nitpick comments (19)
src/components/form/FormField.tsx (1)

36-48: Consider adding accessibility attributes to the label.

The label row renders correctly, but for screen reader support (as mentioned in PR objectives), consider adding accessibilityRole="text" or associating the label with the input using accessibilityLabelledBy/nativeID patterns.

♻️ Suggested accessibility enhancement
       {(label || labelRight) && (
-        <View className="flex-row items-center justify-between mb-1.5">
+        <View className="flex-row items-center justify-between mb-1.5" accessibilityRole="text">
           {label && (
-            <Text className="font-body text-sm text-neutral-300">
+            <Text className="font-body text-sm text-neutral-300" nativeID="field-label">
               {label}
               {required && <Text className="text-coral-500 ml-0.5">*</Text>}
             </Text>
src/components/layout/Divider.tsx (1)

43-58: Potential style conflict when using className with background color.

The style prop sets backgroundColor directly, while className may also contain background color classes. Inline styles take precedence in React Native, so any bg-* class in className would be overridden. Consider documenting this behavior or removing backgroundColor from style when a custom className is provided.

Additionally, the vertical orientation uses height: '100%', which requires the parent container to have an explicit height to render correctly.

src/components/code/FileTree.tsx (1)

148-155: defaultExpanded only initializes state; prop changes are ignored.

This is a common controlled/uncontrolled pattern, but if consumers expect to programmatically expand folders by updating defaultExpanded, it won't work. This behavior is acceptable but should be documented, or consider adding an expanded controlled prop.

src/components/display/Badge.tsx (1)

25-32: primary and error variants have identical styles.

Both variants use bg-coral-500/20 and text-coral-400. If this is intentional for the design system, consider adding a comment. Otherwise, differentiate them for clearer semantic meaning.

src/components/display/EmptyState.tsx (1)

51-65: Add accessibility attributes to the container.

Per PR objectives requiring accessibility features, consider adding accessibilityRole="alert" or accessibilityRole="status" to the container View for screen reader users, especially for ErrorState and NoResults variants.

Suggested improvement
     <View
       className={`items-center justify-center ${styles.padding}`}
+      accessibilityRole="status"
+      accessibilityLabel={`${title}. ${description || ''}`}
     >
src/components/form/Select.tsx (2)

94-118: Add accessibility to option items.

Each option should have appropriate accessibility attributes for screen reader users.

Suggested improvement
                   <Pressable
                     onPress={() => !item.disabled && handleSelect(item.value)}
                     disabled={item.disabled}
                     className={`px-4 py-3 border-b border-neutral-800 ${
                       item.value === value ? 'bg-teal-600/20' : ''
                     } ${item.disabled ? 'opacity-50' : 'active:bg-neutral-700'}`}
+                    accessibilityRole="radio"
+                    accessibilityState={{ 
+                      selected: item.value === value, 
+                      disabled: item.disabled 
+                    }}
+                    accessibilityLabel={item.label}
                   >

192-194: Consider using a stable key instead of option.label.

If two options share the same label (edge case), this will cause React key warnings. Using item.value (already used in keyExtractor for FlatList) or an index fallback would be more robust.

-        <Pressable
-          key={option.label}
+        <Pressable
+          key={`${option.label}-${index}`}
src/components/layout/Container.tsx (1)

68-76: flex: 1 default may cause unexpected layout behavior.

The container always has flex: 1, which causes it to expand to fill available space. This might be unexpected when using size constraints or when the container is meant to shrink to content. Consider making this configurable or removing it for constrained sizes.

Suggested improvement
   const containerStyle: ViewStyle = {
-    flex: 1,
+    flex: size === 'full' ? 1 : undefined,
     padding: paddingValue,
     maxWidth,
     width: '100%',
     alignSelf: center ? 'center' : undefined,
     ...safeAreaStyle,
     ...style,
   };

Alternatively, add a flex prop to let consumers control this behavior.

src/components/feedback/Loading.tsx (1)

53-67: Add accessibility attributes to Spinner.

Loading indicators should announce their state to screen readers.

Suggested improvement
     <View
       className="items-center"
+      accessibilityRole="progressbar"
+      accessibilityLabel={label || "Loading"}
+      accessibilityState={{ busy: true }}
     >
src/components/feedback/Modal.tsx (1)

54-55: Add accessibilityViewIsModal for proper screen reader behavior.

When a modal is visible, screen readers should focus trap within the modal content.

Suggested improvement
     <RNModal
       visible={visible}
       transparent
       animationType="fade"
       onRequestClose={onClose}
+      accessibilityViewIsModal={true}
     >
src/components/feedback/BottomSheet.tsx (2)

192-212: Add accessibility attributes to action options.

Each option should have appropriate accessibility markup.

Suggested improvement
         <Pressable
           key={option.label}
           onPress={() => {
             option.onPress();
             onClose();
           }}
           disabled={option.disabled}
           className={`py-4 ${index > 0 ? 'border-t border-neutral-800' : ''} ${
             option.disabled ? 'opacity-50' : 'active:bg-neutral-800'
           }`}
+          accessibilityRole="button"
+          accessibilityState={{ disabled: option.disabled }}
+          accessibilityLabel={option.label}
         >

53-67: Animation cleanup on unmount needed.

The spring and timing animations should be stopped when the component unmounts or visibility changes to prevent memory leaks.

Suggested improvement
   useEffect(() => {
+    let animation: Animated.CompositeAnimation;
     if (visible) {
-      Animated.spring(translateY, {
+      animation = Animated.spring(translateY, {
         toValue: 0,
         useNativeDriver: true,
         tension: 50,
         friction: 10,
-      }).start();
+      });
     } else {
-      Animated.timing(translateY, {
+      animation = Animated.timing(translateY, {
         toValue: SCREEN_HEIGHT,
         duration: 200,
         useNativeDriver: true,
-      }).start();
+      });
     }
+    animation.start();
+    return () => animation.stop();
   }, [visible, translateY]);
src/components/code/DiffViewer.tsx (1)

84-88: Consider documenting or removing the unimplemented viewMode prop.

The viewMode prop is accepted but immediately voided. While the void _viewMode pattern suppresses the unused variable warning, consumers may expect split mode to work.

Consider either:

  1. Removing the prop until implemented
  2. Adding a console warning when split is passed
  3. Documenting clearly that only unified is currently supported
src/components/form/Radio.tsx (1)

51-52: Consider adding accessibilityRole="radiogroup" to the container.

The individual radio buttons have proper accessibility attributes, but the containing View should have accessibilityRole="radiogroup" to properly convey the group semantics to screen readers.

♿ Proposed accessibility improvement
-    <View className="w-full">
+    <View className="w-full" accessibilityRole="radiogroup" accessibilityLabel={label}>
src/components/form/Switch.tsx (2)

45-52: Optimize useEffect dependency to avoid unnecessary re-renders.

The styles object is recreated on every render (derived from sizeStyles[size]), causing the effect to re-run even when only the object reference changes but values remain the same. Use size directly in the dependency array instead.

♻️ Suggested optimization
  useEffect(() => {
+   const styles = sizeStyles[size];
    Animated.spring(translateX, {
      toValue: value ? styles.width - styles.thumb - styles.offset * 2 : 0,
      useNativeDriver: true,
      tension: 50,
      friction: 8,
    }).start();
- }, [value, translateX, styles]);
+ }, [value, translateX, size]);

55-60: Redundant disabled check in onPress handler.

The disabled prop is already passed to Pressable, which prevents press events when disabled. The !disabled && check in onPress is redundant.

♻️ Minor cleanup
      <Pressable
-       onPress={() => !disabled && onValueChange(!value)}
+       onPress={() => onValueChange(!value)}
        disabled={disabled}
src/components/form/TextArea.tsx (1)

58-81: Consider adding accessibility label for the TextInput.

Other form components in this PR include accessibility attributes. For consistency and screen reader support, consider adding accessibilityLabel that references the label text.

♻️ Add accessibility support
      <TextInput
        {...props}
        value={value?.toString()}
        onChangeText={onChangeText}
        multiline
        maxLength={maxLength}
+       accessibilityLabel={label}
        onFocus={(e) => {
src/components/feedback/Toast.tsx (1)

83-120: Exit animation won’t render because the view is unmounted.

When visible is false you return null, so the slide/fade-out never paints. If you intend an exit animation, keep the view mounted until the hide animation completes.

💡 One way to keep it mounted for the hide animation
-import { useEffect, useRef } from 'react';
+import { useEffect, useRef, useState } from 'react';
...
-  useEffect(() => {
+  const [rendered, setRendered] = useState(visible);
+
+  useEffect(() => {
+    if (visible) setRendered(true);
+  }, [visible]);
+
+  useEffect(() => {
     if (visible) {
       Animated.parallel([
         Animated.spring(translateY, {
           toValue: 0,
           useNativeDriver: true,
           tension: 50,
           friction: 10,
         }),
         Animated.timing(opacity, {
           toValue: 1,
           duration: 200,
           useNativeDriver: true,
         }),
       ]).start();
 
       if (duration > 0) {
         const timer = setTimeout(onDismiss, duration);
         return () => clearTimeout(timer);
       }
     } else {
       Animated.parallel([
         Animated.timing(translateY, {
           toValue: position === 'top' ? -100 : 100,
           duration: 200,
           useNativeDriver: true,
         }),
         Animated.timing(opacity, {
           toValue: 0,
           duration: 200,
           useNativeDriver: true,
         }),
       ]).start();
+      const hideTimer = setTimeout(() => setRendered(false), 200);
+      return () => clearTimeout(hideTimer);
     }
-  }, [visible, duration, onDismiss, translateY, opacity, position]);
+  }, [visible, duration, onDismiss, translateY, opacity, position]);
 
-  if (!visible) return null;
+  if (!rendered) return null;
src/components/layout/Stack.tsx (1)

9-75: Broaden style to StyleProp and merge safely.

style?: ViewStyle disallows common RN usage (arrays, registered styles). Also, spreading ...style can break if a consumer passes an array. Consider StyleProp<ViewStyle> and style={[stackStyle, style]}.

♻️ Suggested refactor
-import { View, type ViewStyle } from 'react-native';
+import { View, type ViewStyle, type StyleProp } from 'react-native';
...
-  style?: ViewStyle;
+  style?: StyleProp<ViewStyle>;
...
   const stackStyle: ViewStyle = {
     flexDirection: direction,
     gap,
     alignItems: alignMap[align],
     justifyContent: justifyMap[justify],
     flexWrap: wrap ? 'wrap' : 'nowrap',
     flex,
-    ...style,
   };
...
-  return (
-    <View style={stackStyle} className={className}>
+  return (
+    <View style={[stackStyle, style]} className={className}>
       {children}
     </View>
   );

Comment on lines 66 to 73
} else {
result.push({
type: 'remove',
content: oldLines[oldIndex],
oldLineNumber: oldIndex + 1,
});
oldIndex++;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Diff algorithm misses additions when lines differ.

When oldLines[oldIndex] !== newLines[newIndex], the current logic only removes the old line without ever adding the new line. This causes insertions to be lost when content changes rather than being purely appended.

For example, comparing "A" with "B" produces only [{type: 'remove', content: 'A'}] but never adds "B".

🐛 Proposed fix to handle additions when lines differ
     } else {
+      // Remove old line
       result.push({
         type: 'remove',
         content: oldLines[oldIndex],
         oldLineNumber: oldIndex + 1,
       });
       oldIndex++;
+      // Add new line
+      result.push({
+        type: 'add',
+        content: newLines[newIndex],
+        newLineNumber: newIndex + 1,
+      });
+      newIndex++;
     }

Note: As the comment on line 34 suggests, consider using a proper diff library (e.g., diff or jsdiff) for production use to handle complex cases like moved blocks and provide optimal diffs.

🤖 Prompt for AI Agents
In `@src/components/code/DiffViewer.tsx` around lines 66 - 73, The diff routine in
DiffViewer (the block manipulating result, oldLines, newLines, oldIndex,
newIndex) only emits a 'remove' when oldLines[oldIndex] !== newLines[newIndex],
dropping the new line; fix by emitting both the removal and the corresponding
addition when lines differ (push a {type: 'remove', ...} for oldLines[oldIndex]
and a {type: 'add', ...} for newLines[newIndex]) and advance both oldIndex and
newIndex accordingly (or implement a simple lookahead to detect pure
insertions/removals); update the logic inside the function that builds result
(e.g., computeDiff / the loop that uses oldIndex/newIndex) so new lines are not
lost.

Comment on lines 141 to 157
<Pressable
onPress={onClose}
className="px-4 py-2 bg-neutral-700 active:bg-neutral-600"
style={{ borderRadius: '8px 10px 8px 12px' }}
>
<Text className="font-body text-white">{cancelText}</Text>
</Pressable>
<Pressable
onPress={() => {
onConfirm();
onClose();
}}
className={`px-4 py-2 ${confirmColor} active:opacity-80`}
style={{ borderRadius: '8px 10px 8px 12px' }}
>
<Text className="font-body text-white font-semibold">{confirmText}</Text>
</Pressable>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix borderRadius on ConfirmDialog buttons.

Proposed fix
           <Pressable
             onPress={onClose}
             className="px-4 py-2 bg-neutral-700 active:bg-neutral-600"
-            style={{ borderRadius: '8px 10px 8px 12px' }}
+            style={{ borderRadius: 10 }}
+            accessibilityRole="button"
           >
             <Text className="font-body text-white">{cancelText}</Text>
           </Pressable>
           <Pressable
             onPress={() => {
               onConfirm();
               onClose();
             }}
             className={`px-4 py-2 ${confirmColor} active:opacity-80`}
-            style={{ borderRadius: '8px 10px 8px 12px' }}
+            style={{ borderRadius: 10 }}
+            accessibilityRole="button"
           >
🤖 Prompt for AI Agents
In `@src/components/feedback/Modal.tsx` around lines 141 - 157, The Pressable
buttons in Modal.tsx use an invalid string value for style.borderRadius ('8px
10px 8px 12px') which React Native doesn't accept; update both Pressable
elements (the cancel and confirm buttons) to use numeric border radius
properties instead — either a single numeric borderRadius (e.g., 8) or explicit
side properties like borderTopLeftRadius, borderTopRightRadius,
borderBottomRightRadius, borderBottomLeftRadius with numeric values; apply the
change to the style prop on both Pressable instances (and consider moving these
styles into a StyleSheet or shared constant for consistency).

jbdevprimary and others added 8 commits January 18, 2026 15:05
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@jbdevprimary
Copy link
Contributor Author

@claude In @src/components/code/DiffViewer.tsx:

  • Around line 66-73: The diff routine in DiffViewer (the block manipulating
    result, oldLines, newLines, oldIndex, newIndex) only emits a 'remove' when
    oldLines[oldIndex] !== newLines[newIndex], dropping the new line; fix by
    emitting both the removal and the corresponding addition when lines differ (push
    a {type: 'remove', ...} for oldLines[oldIndex] and a {type: 'add', ...} for
    newLines[newIndex]) and advance both oldIndex and newIndex accordingly (or
    implement a simple lookahead to detect pure insertions/removals); update the
    logic inside the function that builds result (e.g., computeDiff / the loop that
    uses oldIndex/newIndex) so new lines are not lost.
  • Line 129: The inline style on the View in DiffViewer.tsx uses an invalid CSS
    string for borderRadius; replace style={{ borderRadius: '12px 10px 14px 8px' }}
    with numeric React Native corner props instead—use borderTopLeftRadius: 12,
    borderTopRightRadius: 10, borderBottomRightRadius: 14, and
    borderBottomLeftRadius: 8 (numbers, no "px") so the View's rounded corners
    render correctly.

In @src/components/code/FileTree.tsx:

  • Around line 187-201: The View in the FileTree component is using an invalid
    string for borderRadius; replace the string '12px 10px 14px 8px' with numeric
    React Native style properties (e.g. borderTopLeftRadius, borderTopRightRadius,
    borderBottomRightRadius, borderBottomLeftRadius) on the same View where
    sortedData.map renders FileTreeNode so the radii are numbers (no "px") and match
    the intended asymmetry.

In @src/components/display/Badge.tsx:

  • Around line 63-71: The Badge component's View sets style={{ borderRadius: '6px
    8px 6px 10px' }}, which is invalid in React Native; change the style to use
    numeric values and explicit corner properties (e.g. borderTopLeftRadius,
    borderTopRightRadius, borderBottomRightRadius, borderBottomLeftRadius) or a
    single numeric borderRadius value in the View returned by Badge so the radii are
    numbers instead of a CSS string.

In @src/components/display/EmptyState.tsx:

  • Around line 70-86: The Pressable instances in EmptyState use an invalid string
    borderRadius style ("8px 10px 8px 12px"); update the style on the Pressable(s)
    that render secondaryAction and action to use React Native-compatible numeric
    borderRadius values (e.g., borderRadius: 8) or implement per-corner numeric
    properties (borderTopLeftRadius, borderTopRightRadius, borderBottomRightRadius,
    borderBottomLeftRadius) to approximate the intended shape; adjust the
    secondaryAction and action Pressable styles accordingly and remove the string
    value so RN will honor the radius.

In @src/components/display/Tooltip.tsx:

  • Around line 94-99: The borderRadius style on the Animated.View is using an
    invalid CSS string ('8px 10px 8px 12px'); replace it with React Native numeric
    corner properties (e.g. borderTopLeftRadius, borderTopRightRadius,
    borderBottomRightRadius, borderBottomLeftRadius) using numbers (without 'px')
    and merge them into the style array (the same place where borderRadius is
    currently added), ensuring types line up with getTooltipStyle() and
    Animated.View’s style prop.
  • Around line 53-85: getTooltipStyle currently uses percentage strings and
    numeric translate values (e.g., left: '50%' with transform: [{ translateX: -50
    }]) which don't yield proper centering in React Native; modify the tooltip to
    measure its own size via onLayout (capture measured width/height in state) and
    update getTooltipStyle to compute numeric centering offsets: replace left:
    '50%'/top: '50%' and translateX/Y -50 with numeric left/top values based on
    layout.x/layout.y plus half of the target element, and use transform: [{
    translateX: -measuredWidth/2 }] or translateY: -measuredHeight/2 as appropriate
    in the getTooltipStyle function and the Animated.View that renders the tooltip
    (referencing getTooltipStyle, position, layout and the new
    measuredWidth/measuredHeight state).

In @src/components/feedback/BottomSheet.tsx:

  • Around line 214-221: The inline style on the cancel Pressable uses an invalid
    string for borderRadius ('12px 14px 12px 16px'); update the style in
    BottomSheet.tsx (the Pressable that renders cancel button) to use numeric radius
    values or explicit corner properties (e.g. replace style: { borderRadius: '12px
    14px 12px 16px' } with an object like { borderTopLeftRadius: 12,
    borderTopRightRadius: 14, borderBottomRightRadius: 12, borderBottomLeftRadius:
    16 } or a single numeric borderRadius without 'px') so it conforms to React
    Native style types.
  • Around line 70-92: PanResponder created in the useRef (panResponder /
    PanResponder.create) captures the initial onClose closure and becomes stale if
    onClose changes; fix by storing the latest onClose in a mutable ref (e.g.,
    onCloseRef.current) updated inside the component on each render and replace
    direct calls to onClose inside onPanResponderRelease with onCloseRef.current();
    ensure the ref is declared with useRef and updated before the PanResponder usage
    so translateY and gesture logic remain unchanged.

In @src/components/feedback/Loading.tsx:

  • Around line 90-105: The useEffect in Loading.tsx starts an Animated.loop but
    never stops it on unmount; modify the effect that creates the loop (the
    Animated.loop call using pulseAnim and Animated.timing) to capture the returned
    animation instance (e.g., const animation = Animated.loop(...)), call
    animation.start(), and return a cleanup function that stops the loop
    (animation.stop()) so the pulseAnim loop is torn down when the component
    unmounts; apply the same pattern used to fix Skeleton if present.
  • Around line 132-142: The overlay uses an invalid string borderRadius and lacks
    accessibility/interaction blocking; in the Loading component replace the string
    style with numeric corner radii (e.g., borderTopLeftRadius: 16,
    borderTopRightRadius: 14, borderBottomLeftRadius: 18, borderBottomRightRadius:
  1. for the inner View, and make the outer overlay accessible and modal by
    adding accessible={true}, accessibilityViewIsModal={true}, an accessibilityLabel
    like message ?? 'Loading', and accessibilityLiveRegion="polite" (or
    accessibilityRole="alert") so screen readers announce the loading state; ensure
    the overlay blocks underlying interaction by keeping it covering the screen
    (pointerEvents can remain default/"auto" on the outer View) and set
    accessibilityElementsHidden={false} on the overlay and true on any background
    content if needed.
  • Around line 34-43: The effect creates an infinite Animated.loop with
    Animated.timing and never stops it; update the useEffect that references
    spinValue to capture the returned animation (const animation =
    Animated.loop(...)), call animation.start(), and return a cleanup function that
    calls animation.stop() (and optionally reset spinValue with
    spinValue.setValue(0)) so the animation is properly stopped on component
    unmount; locate this in the useEffect that creates the Animated.loop and uses
    spinValue.

In @src/components/feedback/Modal.tsx:

  • Around line 68-73: The inline style in Modal.tsx is using an invalid CSS
    string for borderRadius ("20px 16px 20px 18px") which React Native doesn't
    accept; update the style in the component (the style prop where isFull is used)
    to use numeric radius properties: set borderRadius to 0 when isFull is true,
    otherwise remove the string and instead set numeric values like
    borderTopLeftRadius, borderTopRightRadius, borderBottomRightRadius, and
    borderBottomLeftRadius to the corresponding numbers (e.g., 20, 16, 20, 18) so
    React Native receives numeric values.
  • Around line 141-157: The Pressable buttons in Modal.tsx use an invalid string
    value for style.borderRadius ('8px 10px 8px 12px') which React Native doesn't
    accept; update both Pressable elements (the cancel and confirm buttons) to use
    numeric border radius properties instead — either a single numeric borderRadius
    (e.g., 8) or explicit side properties like borderTopLeftRadius,
    borderTopRightRadius, borderBottomRightRadius, borderBottomLeftRadius with
    numeric values; apply the change to the style prop on both Pressable instances
    (and consider moving these styles into a StyleSheet or shared constant for
    consistency).

In @src/components/form/Checkbox.tsx:

  • Around line 53-57: The inline style in the Checkbox component uses an invalid
    CSS-style borderRadius string; update the style in Checkbox (where style={{
    width: styles.box, height: styles.box, borderRadius: '4px 6px 4px 6px' }}) to
    use React Native numeric corner properties instead—replace borderRadius with
    borderTopLeftRadius, borderTopRightRadius, borderBottomRightRadius, and
    borderBottomLeftRadius set to the appropriate numeric values (e.g., 4 and 6) so
    the component accepts the radii correctly (same approach used to fix
    Tooltip.tsx).

In @src/components/form/Select.tsx:

  • Around line 57-72: The Pressable used as the select trigger is missing
    accessibility attributes and uses invalid string borderRadius values; update the
    Pressable (and related props like setIsOpen/disabled/hasError/selectedOption) to
    include accessibilityRole="button" (or "combobox" if your platform supports it),
    accessibilityState={{ disabled, expanded: isOpen }}, and an accessibilityLabel
    (e.g., selectedOption?.label || placeholder) so screen readers know purpose and
    state, and replace the string borderRadius with numeric style properties
    (borderTopLeftRadius, borderTopRightRadius, borderBottomLeftRadius,
    borderBottomRightRadius) or a single numeric borderRadius value to fix the React
    Native style error.

In @src/components/form/TextArea.tsx:

  • Around line 74-80: The style object in the TextArea component is setting
    borderRadius to an invalid CSS string; replace that string with numeric
    per-corner React Native props (e.g., borderTopLeftRadius, borderTopRightRadius,
    borderBottomRightRadius, borderBottomLeftRadius) using numbers instead of a
    string and keep the other properties (minHeight, maxHeight, textAlignVertical,
    lineHeight) unchanged so the style object is valid for React Native.

- Fix DiffViewer diff algorithm to emit both 'remove' and 'add' when
  lines differ (was missing additions)
- Convert all borderRadius string values to React Native compatible
  numeric corner radius properties across 13 component files

Components updated:
- DiffViewer: diff algorithm + borderRadius
- FileTree: borderRadius
- Badge: borderRadius
- EmptyState: borderRadius (2 buttons)
- Tooltip: borderRadius
- BottomSheet: borderRadius
- Loading: borderRadius
- Modal: borderRadius (2 buttons)
- Progress: borderRadius
- Toast: borderRadius
- Checkbox: borderRadius
- Select: borderRadius (2 locations)
- TextArea: borderRadius

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[UI] Expand component library to production-ready set

2 participants