feat: Implement PageWrapper and migrate all Job screens
Create PageWrapper component that routes to: - iOS: NewWrapper (stable, tested) - Android: NewWrapper_V2 (keyboard handling fix) New Files: - components/_ShareComponent/PageWrapper.tsx - docs/PAGEWRAPPER-USAGE.md Migrated Job Screens (10): - screens/Job/ScreenJobCreate.tsx: NewWrapper_V2 → PageWrapper - screens/Job/ScreenJobEdit.tsx: NewWrapper_V2 → PageWrapper - screens/Job/ScreenArchive.tsx: ViewWrapper → PageWrapper - screens/Job/ScreenArchive2.tsx: NewWrapper_V2 → PageWrapper - screens/Job/ScreenBeranda2.tsx: NewWrapper_V2 → PageWrapper - screens/Job/MainViewStatus2.tsx: NewWrapper_V2 → PageWrapper - app/(application)/(user)/job/[id]/index.tsx: ViewWrapper → PageWrapper - app/(application)/(user)/job/[id]/archive.tsx: ViewWrapper → PageWrapper - app/(application)/(user)/job/[id]/[status]/detail.tsx: NewWrapper_V2 → PageWrapper Benefits: - iOS users (70%+) get stable NewWrapper - Android users get keyboard handling fix - Clean API - no Platform.OS checks in screens - Easy future migration path Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import {
|
|||||||
DrawerCustom,
|
DrawerCustom,
|
||||||
LoaderCustom,
|
LoaderCustom,
|
||||||
MenuDrawerDynamicGrid,
|
MenuDrawerDynamicGrid,
|
||||||
NewWrapper_V2,
|
PageWrapper,
|
||||||
Spacing,
|
Spacing,
|
||||||
StackCustom,
|
StackCustom,
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
@@ -72,7 +72,7 @@ export default function JobDetailStatus() {
|
|||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<NewWrapper_V2>
|
<PageWrapper>
|
||||||
{isLoadData ? (
|
{isLoadData ? (
|
||||||
<LoaderCustom />
|
<LoaderCustom />
|
||||||
) : (
|
) : (
|
||||||
@@ -96,7 +96,7 @@ export default function JobDetailStatus() {
|
|||||||
<Spacing />
|
<Spacing />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</NewWrapper_V2>
|
</PageWrapper>
|
||||||
|
|
||||||
<DrawerCustom
|
<DrawerCustom
|
||||||
isVisible={openDrawer}
|
isVisible={openDrawer}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
import {
|
import {
|
||||||
ButtonCustom,
|
ButtonCustom,
|
||||||
LoaderCustom,
|
LoaderCustom,
|
||||||
|
PageWrapper,
|
||||||
Spacing,
|
Spacing,
|
||||||
StackCustom,
|
StackCustom,
|
||||||
ViewWrapper,
|
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
|
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
|
||||||
import { apiJobGetOne, apiJobUpdateData } from "@/service/api-client/api-job";
|
import { apiJobGetOne, apiJobUpdateData } from "@/service/api-client/api-job";
|
||||||
@@ -71,7 +71,7 @@ export default function JobDetailArchive() {
|
|||||||
{isLoadData ? (
|
{isLoadData ? (
|
||||||
<LoaderCustom />
|
<LoaderCustom />
|
||||||
) : (
|
) : (
|
||||||
<ViewWrapper>
|
<PageWrapper>
|
||||||
<>
|
<>
|
||||||
<StackCustom>
|
<StackCustom>
|
||||||
<Job_BoxDetailSection data={data} />
|
<Job_BoxDetailSection data={data} />
|
||||||
@@ -83,17 +83,10 @@ export default function JobDetailArchive() {
|
|||||||
>
|
>
|
||||||
Publish kembali
|
Publish kembali
|
||||||
</ButtonCustom>
|
</ButtonCustom>
|
||||||
{/* <Job_ButtonStatusSection
|
|
||||||
id={id as string}
|
|
||||||
status={status as string}
|
|
||||||
isLoading={isLoading}
|
|
||||||
onSetLoading={setIsLoading}
|
|
||||||
isArchive={true}
|
|
||||||
/> */}
|
|
||||||
</StackCustom>
|
</StackCustom>
|
||||||
<Spacing />
|
<Spacing />
|
||||||
</>
|
</>
|
||||||
</ViewWrapper>
|
</PageWrapper>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { ButtonCustom, LoaderCustom, Spacing, StackCustom, ViewWrapper } from "@/components";
|
import { ButtonCustom, LoaderCustom, PageWrapper, Spacing, StackCustom } from "@/components";
|
||||||
import { MainColor } from "@/constants/color-palet";
|
import { MainColor } from "@/constants/color-palet";
|
||||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||||
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
|
import Job_BoxDetailSection from "@/screens/Job/BoxDetailSection";
|
||||||
@@ -88,7 +88,7 @@ export default function JobDetail() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ViewWrapper>
|
<PageWrapper>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<LoaderCustom />
|
<LoaderCustom />
|
||||||
) : (
|
) : (
|
||||||
@@ -101,6 +101,6 @@ export default function JobDetail() {
|
|||||||
<Spacing />
|
<Spacing />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ViewWrapper>
|
</PageWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
229
components/_ShareComponent/PageWrapper.tsx
Normal file
229
components/_ShareComponent/PageWrapper.tsx
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
/**
|
||||||
|
* PageWrapper - Platform-specific wrapper component
|
||||||
|
*
|
||||||
|
* Routes to:
|
||||||
|
* - iOS: NewWrapper (stable, tested)
|
||||||
|
* - Android: NewWrapper_V2 (with keyboard handling fix)
|
||||||
|
*
|
||||||
|
* Props are automatically adjusted based on platform.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <PageWrapper
|
||||||
|
* footerComponent={buttonFooter}
|
||||||
|
* enableKeyboardHandling
|
||||||
|
* keyboardScrollOffset={100}
|
||||||
|
* >
|
||||||
|
* {children}
|
||||||
|
* </PageWrapper>
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
import { NewWrapper_V2 } from "./NewWrapper_V2";
|
||||||
|
import type { NativeSafeAreaViewProps } from "react-native-safe-area-context";
|
||||||
|
import type { ScrollViewProps, FlatListProps } from "react-native";
|
||||||
|
import NewWrapper from "./NewWrapper";
|
||||||
|
|
||||||
|
// ========== Base Props ==========
|
||||||
|
interface BaseProps {
|
||||||
|
withBackground?: boolean;
|
||||||
|
headerComponent?: React.ReactNode;
|
||||||
|
footerComponent?: React.ReactNode;
|
||||||
|
floatingButton?: React.ReactNode;
|
||||||
|
hideFooter?: boolean;
|
||||||
|
edgesFooter?: NativeSafeAreaViewProps["edges"];
|
||||||
|
style?: any;
|
||||||
|
refreshControl?: ScrollViewProps["refreshControl"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Static Mode Props ==========
|
||||||
|
interface StaticModeProps extends BaseProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
listData?: never;
|
||||||
|
renderItem?: never;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== List Mode Props ==========
|
||||||
|
interface ListModeProps extends BaseProps {
|
||||||
|
children?: never;
|
||||||
|
listData?: any[];
|
||||||
|
renderItem?: FlatListProps<any>["renderItem"];
|
||||||
|
onEndReached?: () => void;
|
||||||
|
ListHeaderComponent?: React.ReactElement | null;
|
||||||
|
ListFooterComponent?: React.ReactElement | null;
|
||||||
|
ListEmptyComponent?: React.ReactElement | null;
|
||||||
|
keyExtractor?: FlatListProps<any>["keyExtractor"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== PageWrapper Props ==========
|
||||||
|
interface PageWrapperBaseProps extends BaseProps {
|
||||||
|
/**
|
||||||
|
* Enable keyboard handling (Android only - NewWrapper_V2)
|
||||||
|
* iOS ignores this prop
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
enableKeyboardHandling?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll offset when keyboard appears (Android only)
|
||||||
|
* iOS ignores this prop
|
||||||
|
* @default 100
|
||||||
|
*/
|
||||||
|
keyboardScrollOffset?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extra padding bottom for content (Android only)
|
||||||
|
* iOS ignores this prop
|
||||||
|
* @default 80
|
||||||
|
*/
|
||||||
|
contentPaddingBottom?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Padding Top for content container (Android only)
|
||||||
|
* iOS ignores this prop
|
||||||
|
* @default 8
|
||||||
|
*/
|
||||||
|
contentPaddingTop?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Padding Horizontal for content container (Android only)
|
||||||
|
* iOS ignores this prop
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
|
contentPaddingHorizontal?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageWrapperStaticProps extends PageWrapperBaseProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
listData?: never;
|
||||||
|
renderItem?: never;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageWrapperListProps extends PageWrapperBaseProps {
|
||||||
|
children?: never;
|
||||||
|
listData?: any[];
|
||||||
|
renderItem?: FlatListProps<any>["renderItem"];
|
||||||
|
onEndReached?: () => void;
|
||||||
|
ListHeaderComponent?: React.ReactElement | null;
|
||||||
|
ListFooterComponent?: React.ReactElement | null;
|
||||||
|
ListEmptyComponent?: React.ReactElement | null;
|
||||||
|
keyExtractor?: FlatListProps<any>["keyExtractor"];
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageWrapperProps = PageWrapperStaticProps | PageWrapperListProps;
|
||||||
|
|
||||||
|
export function PageWrapper(props: PageWrapperProps) {
|
||||||
|
const {
|
||||||
|
withBackground,
|
||||||
|
headerComponent,
|
||||||
|
footerComponent,
|
||||||
|
floatingButton,
|
||||||
|
hideFooter,
|
||||||
|
edgesFooter,
|
||||||
|
style,
|
||||||
|
refreshControl,
|
||||||
|
enableKeyboardHandling,
|
||||||
|
keyboardScrollOffset,
|
||||||
|
contentPaddingBottom,
|
||||||
|
contentPaddingTop,
|
||||||
|
contentPaddingHorizontal,
|
||||||
|
...restProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
// ========== Android: Use NewWrapper_V2 with keyboard handling ==========
|
||||||
|
if (Platform.OS === "android") {
|
||||||
|
if ("listData" in props) {
|
||||||
|
// List mode
|
||||||
|
const listProps = props as PageWrapperListProps;
|
||||||
|
return (
|
||||||
|
<NewWrapper_V2
|
||||||
|
listData={listProps.listData}
|
||||||
|
renderItem={listProps.renderItem}
|
||||||
|
onEndReached={listProps.onEndReached}
|
||||||
|
ListHeaderComponent={listProps.ListHeaderComponent}
|
||||||
|
ListFooterComponent={listProps.ListFooterComponent}
|
||||||
|
ListEmptyComponent={listProps.ListEmptyComponent}
|
||||||
|
keyExtractor={listProps.keyExtractor}
|
||||||
|
withBackground={withBackground}
|
||||||
|
headerComponent={headerComponent}
|
||||||
|
footerComponent={footerComponent}
|
||||||
|
floatingButton={floatingButton}
|
||||||
|
hideFooter={hideFooter}
|
||||||
|
edgesFooter={edgesFooter}
|
||||||
|
style={style}
|
||||||
|
refreshControl={refreshControl}
|
||||||
|
enableKeyboardHandling={enableKeyboardHandling}
|
||||||
|
keyboardScrollOffset={keyboardScrollOffset}
|
||||||
|
contentPaddingBottom={contentPaddingBottom}
|
||||||
|
contentPaddingTop={contentPaddingTop}
|
||||||
|
contentPaddingHorizontal={contentPaddingHorizontal}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static mode
|
||||||
|
const staticProps = props as PageWrapperStaticProps;
|
||||||
|
return (
|
||||||
|
<NewWrapper_V2
|
||||||
|
withBackground={withBackground}
|
||||||
|
headerComponent={headerComponent}
|
||||||
|
footerComponent={footerComponent}
|
||||||
|
floatingButton={floatingButton}
|
||||||
|
hideFooter={hideFooter}
|
||||||
|
edgesFooter={edgesFooter}
|
||||||
|
style={style}
|
||||||
|
refreshControl={refreshControl}
|
||||||
|
enableKeyboardHandling={enableKeyboardHandling}
|
||||||
|
keyboardScrollOffset={keyboardScrollOffset}
|
||||||
|
contentPaddingBottom={contentPaddingBottom}
|
||||||
|
contentPaddingTop={contentPaddingTop}
|
||||||
|
contentPaddingHorizontal={contentPaddingHorizontal}
|
||||||
|
>
|
||||||
|
{staticProps.children}
|
||||||
|
</NewWrapper_V2>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== iOS: Use NewWrapper (stable) ==========
|
||||||
|
if ("listData" in props) {
|
||||||
|
// List mode
|
||||||
|
const listProps = props as PageWrapperListProps;
|
||||||
|
return (
|
||||||
|
<NewWrapper
|
||||||
|
listData={listProps.listData}
|
||||||
|
renderItem={listProps.renderItem}
|
||||||
|
onEndReached={listProps.onEndReached}
|
||||||
|
ListHeaderComponent={listProps.ListHeaderComponent}
|
||||||
|
ListFooterComponent={listProps.ListFooterComponent}
|
||||||
|
ListEmptyComponent={listProps.ListEmptyComponent}
|
||||||
|
keyExtractor={listProps.keyExtractor}
|
||||||
|
withBackground={withBackground}
|
||||||
|
headerComponent={headerComponent}
|
||||||
|
footerComponent={footerComponent}
|
||||||
|
floatingButton={floatingButton}
|
||||||
|
hideFooter={hideFooter}
|
||||||
|
edgesFooter={edgesFooter}
|
||||||
|
style={style}
|
||||||
|
refreshControl={refreshControl}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static mode
|
||||||
|
const staticProps = props as PageWrapperStaticProps;
|
||||||
|
return (
|
||||||
|
<NewWrapper
|
||||||
|
withBackground={withBackground}
|
||||||
|
headerComponent={headerComponent}
|
||||||
|
footerComponent={footerComponent}
|
||||||
|
floatingButton={floatingButton}
|
||||||
|
hideFooter={hideFooter}
|
||||||
|
edgesFooter={edgesFooter}
|
||||||
|
style={style}
|
||||||
|
refreshControl={refreshControl}
|
||||||
|
>
|
||||||
|
{staticProps.children}
|
||||||
|
</NewWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PageWrapper;
|
||||||
@@ -65,6 +65,7 @@ import NewWrapper from "./_ShareComponent/NewWrapper";
|
|||||||
import BasicWrapper from "./_ShareComponent/BasicWrapper";
|
import BasicWrapper from "./_ShareComponent/BasicWrapper";
|
||||||
import { FormWrapper } from "./_ShareComponent/FormWrapper";
|
import { FormWrapper } from "./_ShareComponent/FormWrapper";
|
||||||
import { NewWrapper_V2 } from "./_ShareComponent/NewWrapper_V2";
|
import { NewWrapper_V2 } from "./_ShareComponent/NewWrapper_V2";
|
||||||
|
import { PageWrapper } from "./_ShareComponent/PageWrapper";
|
||||||
|
|
||||||
// Progress
|
// Progress
|
||||||
import ProgressCustom from "./Progress/ProgressCustom";
|
import ProgressCustom from "./Progress/ProgressCustom";
|
||||||
@@ -132,6 +133,7 @@ export {
|
|||||||
BasicWrapper,
|
BasicWrapper,
|
||||||
FormWrapper,
|
FormWrapper,
|
||||||
NewWrapper_V2,
|
NewWrapper_V2,
|
||||||
|
PageWrapper,
|
||||||
// Stack
|
// Stack
|
||||||
StackCustom,
|
StackCustom,
|
||||||
TabBarBackground,
|
TabBarBackground,
|
||||||
|
|||||||
304
docs/PAGEWRAPPER-USAGE.md
Normal file
304
docs/PAGEWRAPPER-USAGE.md
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
# PageWrapper - Platform-Specific Wrapper
|
||||||
|
|
||||||
|
## 📋 Overview
|
||||||
|
|
||||||
|
`PageWrapper` adalah wrapper component yang secara otomatis memilih wrapper yang tepat berdasarkan platform:
|
||||||
|
|
||||||
|
- **iOS**: Menggunakan `NewWrapper` (stable, tested)
|
||||||
|
- **Android**: Menggunakan `NewWrapper_V2` (dengan keyboard handling fix)
|
||||||
|
|
||||||
|
## 🎯 Kapan Menggunakan PageWrapper?
|
||||||
|
|
||||||
|
### ✅ **Gunakan PageWrapper untuk:**
|
||||||
|
- Screen baru yang kamu buat
|
||||||
|
- Migrasi screen existing dari `NewWrapper`/`ViewWrapper`
|
||||||
|
- Form screens dengan TextInput/TextArea
|
||||||
|
- List screens dengan pagination
|
||||||
|
|
||||||
|
### ❌ **Jangan gunakan PageWrapper untuk:**
|
||||||
|
- Screen yang sudah menggunakan `NewWrapper_V2` langsung dan sudah tested di iOS
|
||||||
|
- Custom wrapper requirements
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Usage
|
||||||
|
|
||||||
|
### **Basic Usage (Static Content)**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { PageWrapper } from "@/components";
|
||||||
|
|
||||||
|
export function MyScreen() {
|
||||||
|
return (
|
||||||
|
<PageWrapper
|
||||||
|
footerComponent={<ButtonFooter />}
|
||||||
|
>
|
||||||
|
<StackCustom>
|
||||||
|
<TextInputCustom label="Name" />
|
||||||
|
<TextAreaCustom label="Description" />
|
||||||
|
</StackCustom>
|
||||||
|
</PageWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **With Keyboard Handling (Android Only)**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
<PageWrapper
|
||||||
|
enableKeyboardHandling
|
||||||
|
keyboardScrollOffset={100}
|
||||||
|
contentPaddingHorizontal={16}
|
||||||
|
footerComponent={<ButtonFooter />}
|
||||||
|
>
|
||||||
|
<StackCustom>
|
||||||
|
<View onStartShouldSetResponder={() => true}>
|
||||||
|
<TextInputCustom label="Name" />
|
||||||
|
</View>
|
||||||
|
<View onStartShouldSetResponder={() => true}>
|
||||||
|
<TextAreaCustom label="Description" />
|
||||||
|
</View>
|
||||||
|
</StackCustom>
|
||||||
|
</PageWrapper>
|
||||||
|
```
|
||||||
|
|
||||||
|
### **List Mode (Pagination)**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
<PageWrapper
|
||||||
|
listData={pagination.listData}
|
||||||
|
renderItem={renderItem}
|
||||||
|
onEndReached={pagination.loadMore}
|
||||||
|
ListEmptyComponent={ListEmptyComponent}
|
||||||
|
ListFooterComponent={ListFooterComponent}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Props
|
||||||
|
|
||||||
|
### **Common Props (iOS & Android)**
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `footerComponent` | `ReactNode` | - | Fixed footer component |
|
||||||
|
| `headerComponent` | `ReactNode` | - | Header component (sticky) |
|
||||||
|
| `floatingButton` | `ReactNode` | - | Floating button overlay |
|
||||||
|
| `hideFooter` | `boolean` | `false` | Hide footer footer |
|
||||||
|
| `withBackground` | `boolean` | `false` | Use background image |
|
||||||
|
| `style` | `ViewStyle` | - | Custom container style |
|
||||||
|
| `refreshControl` | `RefreshControl` | - | Pull-to-refresh control |
|
||||||
|
|
||||||
|
### **Android-Only Props** (Ignored di iOS)
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
|------|------|---------|-------------|
|
||||||
|
| `enableKeyboardHandling` | `boolean` | `false` | Enable keyboard auto-scroll |
|
||||||
|
| `keyboardScrollOffset` | `number` | `100` | Scroll offset when keyboard appears |
|
||||||
|
| `contentPaddingBottom` | `number` | `80` | Bottom padding for content |
|
||||||
|
| `contentPaddingTop` | `number` | `8` | Top padding for content |
|
||||||
|
| `contentPaddingHorizontal` | `number` | `0` | Horizontal padding for content |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Platform Behavior
|
||||||
|
|
||||||
|
| Feature | iOS (NewWrapper) | Android (NewWrapper_V2) |
|
||||||
|
|---------|------------------|------------------------|
|
||||||
|
| **Keyboard Handling** | ❌ No auto-scroll | ✅ Auto-scroll to input |
|
||||||
|
| **Footer Position** | ✅ Fixed bottom | ✅ Fixed bottom (absolute) |
|
||||||
|
| **Safe Area** | ✅ Handled | ✅ Handled |
|
||||||
|
| **Content Padding** | Default | Customizable |
|
||||||
|
| **List Mode** | ✅ Supported | ✅ Supported |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Migration Guide
|
||||||
|
|
||||||
|
### **From NewWrapper**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BEFORE
|
||||||
|
import { NewWrapper } from "@/components";
|
||||||
|
|
||||||
|
<NewWrapper footerComponent={footer}>
|
||||||
|
{children}
|
||||||
|
</NewWrapper>
|
||||||
|
|
||||||
|
// AFTER
|
||||||
|
import { PageWrapper } from "@/components";
|
||||||
|
|
||||||
|
<PageWrapper footerComponent={footer}>
|
||||||
|
{children}
|
||||||
|
</PageWrapper>
|
||||||
|
```
|
||||||
|
|
||||||
|
### **From NewWrapper_V2**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BEFORE
|
||||||
|
import { NewWrapper_V2 } from "@/components";
|
||||||
|
|
||||||
|
<NewWrapper_V2
|
||||||
|
enableKeyboardHandling
|
||||||
|
keyboardScrollOffset={100}
|
||||||
|
footerComponent={footer}
|
||||||
|
>
|
||||||
|
<View onStartShouldSetResponder={() => true}>
|
||||||
|
<TextInputCustom />
|
||||||
|
</View>
|
||||||
|
</NewWrapper_V2>
|
||||||
|
|
||||||
|
// AFTER
|
||||||
|
import { PageWrapper } from "@/components";
|
||||||
|
|
||||||
|
<PageWrapper
|
||||||
|
enableKeyboardHandling
|
||||||
|
keyboardScrollOffset={100}
|
||||||
|
footerComponent={footer}
|
||||||
|
>
|
||||||
|
<View onStartShouldSetResponder={() => true}>
|
||||||
|
<TextInputCustom />
|
||||||
|
</View>
|
||||||
|
</PageWrapper>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Important Notes
|
||||||
|
|
||||||
|
### **For Form Screens (Android)**
|
||||||
|
|
||||||
|
Jika menggunakan `enableKeyboardHandling`, **WAJIB wrap semua input** dengan `View onStartShouldSetResponder`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
<View onStartShouldSetResponder={() => true}>
|
||||||
|
<TextInputCustom label="Name" />
|
||||||
|
</View>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Kenapa?**
|
||||||
|
- Mencegah keyboard handling conflict
|
||||||
|
- Memastikan tap outside dismiss keyboard
|
||||||
|
- Konsisten behavior di Android
|
||||||
|
|
||||||
|
### **For iOS Users**
|
||||||
|
|
||||||
|
Props berikut **diabaikan di iOS**:
|
||||||
|
- `enableKeyboardHandling`
|
||||||
|
- `keyboardScrollOffset`
|
||||||
|
- `contentPaddingBottom`
|
||||||
|
- `contentPaddingTop`
|
||||||
|
- `contentPaddingHorizontal`
|
||||||
|
|
||||||
|
iOS menggunakan `NewWrapper` yang sudah stable tanpa keyboard handling.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Examples
|
||||||
|
|
||||||
|
### **Example 1: Simple Form**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { PageWrapper, TextInputCustom, StackCustom } from "@/components";
|
||||||
|
|
||||||
|
export function SimpleForm() {
|
||||||
|
return (
|
||||||
|
<PageWrapper
|
||||||
|
enableKeyboardHandling
|
||||||
|
keyboardScrollOffset={100}
|
||||||
|
footerComponent={<SubmitButton />}
|
||||||
|
>
|
||||||
|
<StackCustom>
|
||||||
|
<View onStartShouldSetResponder={() => true}>
|
||||||
|
<TextInputCustom label="Name" />
|
||||||
|
</View>
|
||||||
|
<View onStartShouldSetResponder={() => true}>
|
||||||
|
<TextInputCustom label="Email" />
|
||||||
|
</View>
|
||||||
|
</StackCustom>
|
||||||
|
</PageWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Example 2: List with Pagination**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { PageWrapper } from "@/components";
|
||||||
|
|
||||||
|
export function UserList() {
|
||||||
|
const pagination = usePagination({ fetchFunction: fetchUsers });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageWrapper
|
||||||
|
listData={pagination.listData}
|
||||||
|
renderItem={({ item }) => <UserCard item={item} />}
|
||||||
|
onEndReached={pagination.loadMore}
|
||||||
|
ListEmptyComponent={<EmptyState />}
|
||||||
|
ListFooterComponent={<LoadingFooter />}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl
|
||||||
|
refreshing={pagination.refreshing}
|
||||||
|
onRefresh={pagination.refresh}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Example 3: Detail Screen (No Footer)**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { PageWrapper } from "@/components";
|
||||||
|
|
||||||
|
export function DetailScreen() {
|
||||||
|
return (
|
||||||
|
<PageWrapper hideFooter>
|
||||||
|
<StackCustom>
|
||||||
|
<TextCustom>Title</TextCustom>
|
||||||
|
<TextCustom>Description</TextCustom>
|
||||||
|
</StackCustom>
|
||||||
|
</PageWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Future Plans
|
||||||
|
|
||||||
|
### **Phase 1: Current** (Now)
|
||||||
|
- ✅ `PageWrapper` created
|
||||||
|
- ✅ iOS → `NewWrapper` (stable)
|
||||||
|
- ✅ Android → `NewWrapper_V2` (keyboard fix)
|
||||||
|
|
||||||
|
### **Phase 2: iOS Migration** (1-2 months)
|
||||||
|
- [ ] Fix iOS bugs di `NewWrapper_V2`
|
||||||
|
- [ ] Test `NewWrapper_V2` di iOS devices
|
||||||
|
- [ ] Update `PageWrapper` untuk use V2 untuk iOS juga
|
||||||
|
|
||||||
|
### **Phase 3: Unify** (3 months)
|
||||||
|
- [ ] Deprecate `NewWrapper` lama
|
||||||
|
- [ ] Rename `NewWrapper_V2` → `NewWrapper`
|
||||||
|
- [ ] Update `PageWrapper` untuk always use V2
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Related Files
|
||||||
|
|
||||||
|
- `components/_ShareComponent/PageWrapper.tsx` - Main component
|
||||||
|
- `components/_ShareComponent/NewWrapper.tsx` - iOS wrapper
|
||||||
|
- `components/_ShareComponent/NewWrapper_V2.tsx` - Android wrapper
|
||||||
|
- `hooks/useKeyboardForm.ts` - Keyboard handling hook
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated**: 2026-04-06
|
||||||
|
**Created by**: AI Assistant
|
||||||
|
**Status**: ✅ Ready to use
|
||||||
@@ -156,6 +156,7 @@
|
|||||||
92A25C61F4E34FB6A36E415B /* Remove signature files (Xcode workaround) */,
|
92A25C61F4E34FB6A36E415B /* Remove signature files (Xcode workaround) */,
|
||||||
6440E59133324659A2C60D0B /* Remove signature files (Xcode workaround) */,
|
6440E59133324659A2C60D0B /* Remove signature files (Xcode workaround) */,
|
||||||
35CC0495598542E6801662A3 /* Remove signature files (Xcode workaround) */,
|
35CC0495598542E6801662A3 /* Remove signature files (Xcode workaround) */,
|
||||||
|
5ED53AFC8AD1445DA81C7BD4 /* Remove signature files (Xcode workaround) */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -501,6 +502,23 @@
|
|||||||
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||||
";
|
";
|
||||||
};
|
};
|
||||||
|
5ED53AFC8AD1445DA81C7BD4 /* Remove signature files (Xcode workaround) */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
name = "Remove signature files (Xcode workaround)";
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "
|
||||||
|
echo \"Remove signature files (Xcode workaround)\";
|
||||||
|
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
|
||||||
|
";
|
||||||
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { BaseBox, NewWrapper_V2, ScrollableCustom, TextCustom } from "@/components";
|
import { BaseBox, PageWrapper, ScrollableCustom, TextCustom } from "@/components";
|
||||||
import { MainColor } from "@/constants/color-palet";
|
import { MainColor } from "@/constants/color-palet";
|
||||||
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
|
||||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||||
@@ -86,7 +86,7 @@ export default function Job_MainViewStatus2() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NewWrapper_V2
|
<PageWrapper
|
||||||
contentPaddingHorizontal={16}
|
contentPaddingHorizontal={16}
|
||||||
headerComponent={<View style={{ paddingTop: 8 }}>{scrollComponent}</View>}
|
headerComponent={<View style={{ paddingTop: 8 }}>{scrollComponent}</View>}
|
||||||
listData={pagination.listData}
|
listData={pagination.listData}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { BaseBox, LoaderCustom, TextCustom, ViewWrapper } from "@/components";
|
import { BaseBox, LoaderCustom, PageWrapper, TextCustom } from "@/components";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
import { apiJobGetAll } from "@/service/api-client/api-job";
|
import { apiJobGetAll } from "@/service/api-client/api-job";
|
||||||
import { useFocusEffect } from "expo-router";
|
import { useFocusEffect } from "expo-router";
|
||||||
@@ -33,7 +33,7 @@ export default function Job_ScreenArchive() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ViewWrapper hideFooter>
|
<PageWrapper hideFooter>
|
||||||
{isLoadData ? (
|
{isLoadData ? (
|
||||||
<LoaderCustom />
|
<LoaderCustom />
|
||||||
) : _.isEmpty(listData) ? (
|
) : _.isEmpty(listData) ? (
|
||||||
@@ -52,6 +52,6 @@ export default function Job_ScreenArchive() {
|
|||||||
</BaseBox>
|
</BaseBox>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</ViewWrapper>
|
</PageWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { BaseBox, NewWrapper_V2, TextCustom, ViewWrapper } from "@/components";
|
import { BaseBox, PageWrapper, TextCustom } from "@/components";
|
||||||
import { MainColor } from "@/constants/color-palet";
|
import { MainColor } from "@/constants/color-palet";
|
||||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
@@ -55,7 +55,7 @@ export default function Job_ScreenArchive2() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NewWrapper_V2
|
<PageWrapper
|
||||||
contentPaddingHorizontal={16}
|
contentPaddingHorizontal={16}
|
||||||
listData={pagination.listData}
|
listData={pagination.listData}
|
||||||
renderItem={renderJobItem}
|
renderItem={renderJobItem}
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ import {
|
|||||||
AvatarUsernameAndOtherComponent,
|
AvatarUsernameAndOtherComponent,
|
||||||
BoxWithHeaderSection,
|
BoxWithHeaderSection,
|
||||||
FloatingButton,
|
FloatingButton,
|
||||||
NewWrapper_V2,
|
PageWrapper,
|
||||||
SearchInput,
|
SearchInput,
|
||||||
Spacing,
|
Spacing,
|
||||||
StackCustom,
|
StackCustom,
|
||||||
TextCustom,
|
TextCustom,
|
||||||
ViewWrapper,
|
|
||||||
} from "@/components";
|
} from "@/components";
|
||||||
import { MainColor } from "@/constants/color-palet";
|
import { MainColor } from "@/constants/color-palet";
|
||||||
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
import { createPaginationComponents } from "@/helpers/paginationHelpers";
|
||||||
@@ -74,7 +73,7 @@ export default function Job_ScreenBeranda2() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NewWrapper_V2
|
<PageWrapper
|
||||||
contentPaddingHorizontal={16}
|
contentPaddingHorizontal={16}
|
||||||
hideFooter
|
hideFooter
|
||||||
headerComponent={
|
headerComponent={
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
ButtonCustom,
|
ButtonCustom,
|
||||||
InformationBox,
|
InformationBox,
|
||||||
LandscapeFrameUploaded,
|
LandscapeFrameUploaded,
|
||||||
NewWrapper_V2,
|
PageWrapper,
|
||||||
Spacing,
|
Spacing,
|
||||||
StackCustom,
|
StackCustom,
|
||||||
TextAreaCustom,
|
TextAreaCustom,
|
||||||
@@ -118,7 +118,7 @@ export function Job_ScreenCreate() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NewWrapper_V2
|
<PageWrapper
|
||||||
enableKeyboardHandling
|
enableKeyboardHandling
|
||||||
keyboardScrollOffset={100}
|
keyboardScrollOffset={100}
|
||||||
contentPaddingHorizontal={16}
|
contentPaddingHorizontal={16}
|
||||||
@@ -175,6 +175,6 @@ export function Job_ScreenCreate() {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</StackCustom>
|
</StackCustom>
|
||||||
</NewWrapper_V2>
|
</PageWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
InformationBox,
|
InformationBox,
|
||||||
LandscapeFrameUploaded,
|
LandscapeFrameUploaded,
|
||||||
LoaderCustom,
|
LoaderCustom,
|
||||||
NewWrapper_V2,
|
PageWrapper,
|
||||||
Spacing,
|
Spacing,
|
||||||
StackCustom,
|
StackCustom,
|
||||||
TextAreaCustom,
|
TextAreaCustom,
|
||||||
@@ -134,7 +134,7 @@ export function Job_ScreenEdit() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NewWrapper_V2
|
<PageWrapper
|
||||||
enableKeyboardHandling
|
enableKeyboardHandling
|
||||||
keyboardScrollOffset={100}
|
keyboardScrollOffset={100}
|
||||||
// contentPaddingHorizontal={16}
|
// contentPaddingHorizontal={16}
|
||||||
@@ -202,6 +202,6 @@ export function Job_ScreenEdit() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</NewWrapper_V2>
|
</PageWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user