11import React , { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
2+ import { getDefaultSheetZoomPreference } from '../lib/preferences' ;
23import {
34 ActivityIndicator ,
45 Animated ,
@@ -14,6 +15,10 @@ import {
1415export const GLASS_BG_DARK = 'rgba(255, 255, 255, 0.01)' ;
1516export const GLASS_BG_LIGHT = 'rgba(0, 0, 0, 0.01)' ;
1617
18+ // Sheet header background colors - exported for parent to match tab bar
19+ export const SHEET_HEADER_BG_DARK = '#252629' ;
20+ export const SHEET_HEADER_BG_LIGHT = '#f5f5f5' ;
21+
1722// Excel date serial number range (1900-2099)
1823const MIN_DATE_SERIAL = 1 ;
1924const MAX_DATE_SERIAL = 73050 ;
@@ -117,6 +122,7 @@ export interface SheetControlsData {
117122 tabBarHeight : number ;
118123 onZoomIn : ( ) => void ;
119124 onZoomOut : ( ) => void ;
125+ onZoomReset : ( ) => void ;
120126 onSelectSheet : ( index : number ) => void ;
121127}
122128
@@ -134,7 +140,8 @@ interface NativeSheetsViewerProps {
134140 } ;
135141 onLoaded ?: ( ) => void ;
136142 hideLoadingOverlay ?: boolean ;
137- bottomInset ?: number ;
143+ /** Extra top inset for sheet tabs when present */
144+ topInset ?: number ;
138145 /** Callback to provide sheet controls data to parent for rendering outside ScrollView */
139146 onControlsReady ?: ( controls : SheetControlsData ) => void ;
140147}
@@ -146,7 +153,7 @@ const BASE_ROW_HEADER_WIDTH = 36;
146153const BASE_COLUMN_HEADER_HEIGHT = 20 ;
147154const HEADER_FONT_SIZE = 10 ;
148155const BUFFER = 30 ;
149- const MIN_ZOOM = 0.5 ;
156+ const MIN_ZOOM = 0.50 ;
150157const MAX_ZOOM = 3.0 ;
151158const DEFAULT_ZOOM = 1.0 ;
152159
@@ -265,13 +272,29 @@ export function NativeSheetsViewer({
265272 theme,
266273 onLoaded,
267274 hideLoadingOverlay,
268- bottomInset = 0 ,
275+ topInset = 0 ,
269276 onControlsReady,
270277} : NativeSheetsViewerProps ) {
271278 const [ loading , setLoading ] = useState ( true ) ;
272279 const [ activeSheetIndex , setActiveSheetIndex ] = useState ( 0 ) ;
273280 const [ zoom , setZoom ] = useState ( DEFAULT_ZOOM ) ;
274281
282+ // Load saved zoom preference on mount
283+ useEffect ( ( ) => {
284+ const loadZoomPreference = async ( ) => {
285+ try {
286+ const savedZoom = await getDefaultSheetZoomPreference ( ) ;
287+ setZoom ( savedZoom ) ;
288+ } catch ( error ) {
289+ // Keep default zoom on error
290+ if ( __DEV__ ) {
291+ console . error ( 'Failed to load sheet zoom preference:' , error ) ;
292+ }
293+ }
294+ } ;
295+ loadZoomPreference ( ) ;
296+ } , [ ] ) ;
297+
275298 // Track scroll for virtualization only (not for header sync)
276299 const scrollXRef = useRef ( 0 ) ;
277300 const scrollYRef = useRef ( 0 ) ;
@@ -340,8 +363,9 @@ export function NativeSheetsViewer({
340363 }
341364
342365 // Ensure minimum visible area for empty or small sheets
366+ // Add 10 extra rows beyond the data
343367 return {
344- maxRow : Math . max ( MIN_ROWS , maxRow + 3 ) ,
368+ maxRow : Math . max ( MIN_ROWS , maxRow + 10 ) ,
345369 maxCol : Math . max ( MIN_COLS , maxCol + 2 )
346370 } ;
347371 } , [ currentSheet ?. cellData ] ) ;
@@ -449,6 +473,10 @@ export function NativeSheetsViewer({
449473 setZoom ( z => Math . max ( MIN_ZOOM , z - 0.25 ) ) ;
450474 } , [ ] ) ;
451475
476+ const handleZoomReset = useCallback ( ( ) => {
477+ setZoom ( DEFAULT_ZOOM ) ;
478+ } , [ ] ) ;
479+
452480 const handleSelectSheet = useCallback ( ( index : number ) => {
453481 setActiveSheetIndex ( index ) ;
454482 } , [ ] ) ;
@@ -464,7 +492,8 @@ export function NativeSheetsViewer({
464492 const borderColor = theme . isDark ? '#444' : '#ddd' ;
465493 const headerBg = theme . isDark ? '#252629' : '#f5f5f5' ;
466494 const cellBg = theme . colors . background ;
467- const tabBarHeight = hasMultipleSheets ? 48 + bottomInset : 0 ;
495+ // Tabs are now at top (rendered by parent), so no bottom space needed
496+ const tabBarHeight = 0 ;
468497
469498 // Notify parent about controls data so it can render glass controls outside ScrollView
470499 useEffect ( ( ) => {
@@ -479,10 +508,11 @@ export function NativeSheetsViewer({
479508 tabBarHeight,
480509 onZoomIn : handleZoomIn ,
481510 onZoomOut : handleZoomOut ,
511+ onZoomReset : handleZoomReset ,
482512 onSelectSheet : handleSelectSheet ,
483513 } ) ;
484514 }
485- } , [ currentSheet , onControlsReady , zoom , sheetInfo . names , activeSheetIndex , hasMultipleSheets , tabBarHeight , handleZoomIn , handleZoomOut , handleSelectSheet ] ) ;
515+ } , [ currentSheet , onControlsReady , zoom , sheetInfo . names , activeSheetIndex , hasMultipleSheets , tabBarHeight , handleZoomIn , handleZoomOut , handleZoomReset , handleSelectSheet ] ) ;
486516
487517 if ( ! workbook || ! currentSheet ) {
488518 return (
@@ -779,6 +809,7 @@ export function NativeSheetsViewer({
779809 style = { [
780810 styles . corner ,
781811 {
812+ top : topInset ,
782813 width : ROW_HEADER_WIDTH ,
783814 height : COLUMN_HEADER_HEIGHT ,
784815 backgroundColor : headerBg ,
@@ -789,7 +820,7 @@ export function NativeSheetsViewer({
789820 />
790821
791822 { /* Column headers - synced via Animated transform */ }
792- < View style = { [ styles . columnHeaderWrapper , { left : ROW_HEADER_WIDTH , height : COLUMN_HEADER_HEIGHT } ] } >
823+ < View style = { [ styles . columnHeaderWrapper , { top : topInset , left : ROW_HEADER_WIDTH , height : COLUMN_HEADER_HEIGHT } ] } >
793824 < Animated . View
794825 style = { {
795826 flexDirection : 'row' ,
@@ -801,7 +832,7 @@ export function NativeSheetsViewer({
801832 </ View >
802833
803834 { /* Row headers - synced via Animated transform */ }
804- < View style = { [ styles . rowHeaderWrapper , { top : COLUMN_HEADER_HEIGHT , bottom : tabBarHeight , width : ROW_HEADER_WIDTH } ] } >
835+ < View style = { [ styles . rowHeaderWrapper , { top : topInset + COLUMN_HEADER_HEIGHT , bottom : tabBarHeight , width : ROW_HEADER_WIDTH } ] } >
805836 < Animated . View
806837 style = { {
807838 transform : [ { translateY : Animated . multiply ( scrollYAnim , - 1 ) } ] ,
@@ -812,7 +843,7 @@ export function NativeSheetsViewer({
812843 </ View >
813844
814845 { /* Main scrollable grid */ }
815- < View style = { [ styles . gridContainer , { top : COLUMN_HEADER_HEIGHT , left : ROW_HEADER_WIDTH , bottom : tabBarHeight } ] } >
846+ < View style = { [ styles . gridContainer , { top : topInset + COLUMN_HEADER_HEIGHT , left : ROW_HEADER_WIDTH , bottom : tabBarHeight } ] } >
816847 < Animated . ScrollView
817848 horizontal
818849 showsHorizontalScrollIndicator = { false }
@@ -877,15 +908,13 @@ const styles = StyleSheet.create({
877908 } ,
878909 corner : {
879910 position : 'absolute' ,
880- top : 0 ,
881911 left : 0 ,
882912 zIndex : 10 ,
883913 borderRightWidth : StyleSheet . hairlineWidth ,
884914 borderBottomWidth : StyleSheet . hairlineWidth ,
885915 } ,
886916 columnHeaderWrapper : {
887917 position : 'absolute' ,
888- top : 0 ,
889918 right : 0 ,
890919 zIndex : 5 ,
891920 overflow : 'hidden' ,
0 commit comments