Peace 16 hours ago
parent 35b5f45de9
commit 854af00711
  1. 41
      my-expo-app--paper/.gitignore
  2. 22
      my-expo-app--paper/App.tsx
  3. 30
      my-expo-app--paper/app.json
  4. BIN
      my-expo-app--paper/assets/adaptive-icon.png
  5. BIN
      my-expo-app--paper/assets/favicon.png
  6. BIN
      my-expo-app--paper/assets/icon.png
  7. BIN
      my-expo-app--paper/assets/splash-icon.png
  8. 71
      my-expo-app--paper/components/stack.tsx
  9. 17
      my-expo-app--paper/eslint.config.js
  10. 8
      my-expo-app--paper/index.ts
  11. 13379
      my-expo-app--paper/package-lock.json
  12. 28
      my-expo-app--paper/package.json
  13. 51
      my-expo-app--paper/pages/TestScreen.tsx
  14. 10
      my-expo-app--paper/prettier.config.js
  15. 37
      my-expo-app--paper/src/animation/useBounce.ts
  16. 71
      my-expo-app--paper/src/theme.ts
  17. 55
      my-expo-app--paper/stores/CounterStore.tsx
  18. 6
      my-expo-app--paper/tsconfig.json

@ -0,0 +1,41 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
expo-env.d.ts
# Native
.kotlin/
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo
# generated native folders
/ios
/android

@ -0,0 +1,22 @@
import { StatusBar } from 'expo-status-bar';
import { useState } from 'react';
import { PaperProvider } from 'react-native-paper';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import { appTheme } from './src/theme';
import TestPage from './pages/TestScreen';
import { CounterProvider } from './stores/CounterStore';
export default function App() {
return (
<PaperProvider theme={appTheme}>
<SafeAreaProvider>
<SafeAreaView style={{ flex: 1 }}>
<StatusBar style="auto" />
<CounterProvider>
<TestPage />
</CounterProvider>
</SafeAreaView>
</SafeAreaProvider>
</PaperProvider>
);
}

@ -0,0 +1,30 @@
{
"expo": {
"name": "my-expo-app--paper",
"slug": "my-expo-app--paper",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"newArchEnabled": true,
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@ -0,0 +1,71 @@
import { View, ViewStyle, StyleProp } from 'react-native';
type Align = 'flex-start' | 'center' | 'flex-end' | 'stretch' | 'baseline';
type Justify =
| 'flex-start'
| 'center'
| 'flex-end'
| 'space-between'
| 'space-around'
| 'space-evenly';
type StackProps = {
gap?: number;
align?: Align;
justify?: Justify;
wrap?: boolean;
style?: StyleProp<ViewStyle>;
children?: React.ReactNode;
};
export function HStack({
gap = 0,
align = 'center',
justify = 'flex-start',
wrap = false,
style,
children,
}: StackProps) {
return (
<View
style={[
{
flexDirection: 'row',
alignItems: align,
justifyContent: justify,
gap,
flexWrap: wrap ? 'wrap' : 'nowrap',
},
style,
]}>
{children}
</View>
);
}
export function VStack({
gap = 0,
align = 'stretch',
justify = 'flex-start',
wrap = false,
style,
children,
}: StackProps) {
return (
<View
style={[
{
flexDirection: 'column',
alignItems: align,
justifyContent: justify,
gap,
flexWrap: wrap ? 'wrap' : 'nowrap',
},
style,
]}>
{children}
</View>
);
}
export const Spacer = ({ flex = 1 }: { flex?: number }) => <View style={{ flex }} />;

@ -0,0 +1,17 @@
/* eslint-env node */
const { defineConfig } = require('eslint/config');
const expoConfig = require('eslint-config-expo/flat');
const prettierConfig = require('eslint-config-prettier');
module.exports = defineConfig([
...expoConfig,
{
ignores: ['dist/*'],
},
{
rules: {
'react/display-name': 'off',
},
},
prettierConfig,
]);

@ -0,0 +1,8 @@
import { registerRootComponent } from 'expo';
import App from './App';
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

File diff suppressed because it is too large Load Diff

@ -0,0 +1,28 @@
{
"name": "my-expo-app--paper",
"version": "1.0.0",
"main": "index.ts",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"expo": "~54.0.9",
"expo-status-bar": "~3.0.8",
"react": "19.1.0",
"react-native": "0.81.4",
"react-native-paper": "^5.14.5",
"react-native-reanimated": "~4.1.0",
"react-native-safe-area-context": "~5.6.0"
},
"devDependencies": {
"@types/react": "~19.1.0",
"eslint": "^9.35.0",
"eslint-config-expo": "^10.0.0",
"eslint-config-prettier": "^10.1.8",
"typescript": "~5.9.2"
},
"private": true
}

@ -0,0 +1,51 @@
import { View } from 'react-native';
import { useCounterDispatch, useCounterState } from '../stores/CounterStore';
import { styles } from '../src/theme';
import { Button, Card, Text } from 'react-native-paper';
import { HStack, VStack } from '../components/stack';
import { useBounce } from '../src/animation/useBounce';
import Animated from 'react-native-reanimated';
export default function TestPage() {
const state = useCounterState();
const dispatch = useCounterDispatch();
const { style: bounceStyle, trigger: bounce } = useBounce({ amplitude: 16, duration: 200 });
return (
<View style={styles.flexCenter}>
<Card mode="outlined" style={{ width: '80%' }}>
<Card.Title title={`Count: ${state.count}`} />
<Card.Content>
<VStack gap={12}>
<HStack justify="space-between">
<Text variant="bodyMedium" style={{ color: '#0000FF' }}>
Text1
</Text>
<Text variant="bodyMedium" style={{ color: '#FF0000' }}>
Text2
</Text>
</HStack>
<Animated.View style={bounceStyle}>
<Button
mode="outlined"
onPress={() => {
bounce();
}}>
🍞Action!
</Button>
</Animated.View>
<HStack justify="space-between">
<Button mode="contained" onPress={() => dispatch({ type: 'INCREMENT' })}>
+
</Button>
<Button mode="contained" onPress={() => dispatch({ type: 'DECREMENT' })}>
-
</Button>
</HStack>
</VStack>
</Card.Content>
</Card>
</View>
);
}

@ -0,0 +1,10 @@
module.exports = {
printWidth: 100,
tabWidth: 2,
singleQuote: true,
bracketSameLine: true,
trailingComma: 'es5',
semi: true,
arrowParens: 'always',
endOfLine: 'auto',
};

@ -0,0 +1,37 @@
import {
useSharedValue,
useAnimatedStyle,
withSequence,
withTiming,
} from 'react-native-reanimated';
type UseBounceOptions = {
amplitude?: number; // 최대 위/아래 이동량(px)
duration?: number; // 전체 지속시간(ms)
};
export function useBounce({ amplitude = 16, duration = 200 }: UseBounceOptions = {}) {
const y = useSharedValue(0);
const style = useAnimatedStyle(
() => ({
transform: [{ translateY: y.value }],
}),
[]
);
const trigger = () => {
'worklet';
const d = duration;
const A = amplitude;
y.value = 0;
y.value = withSequence(
withTiming(-A, { duration: d * 0.3 }),
withTiming(A * 0.25, { duration: d * 0.3 }),
withTiming(-A * 0.5, { duration: d * 0.2 }),
withTiming(0, { duration: d * 0.2 })
);
};
return { style, trigger, y };
}

@ -0,0 +1,71 @@
import { MD3LightTheme, MD3Theme } from 'react-native-paper';
import { StyleSheet } from 'react-native';
export const spacing = { xs: 6, sm: 8, md: 12, lg: 16, xl: 20 } as const;
export const radius = { sm: 2, md: 6, lg: 12 } as const;
export const appTheme: MD3Theme = {
...MD3LightTheme,
roundness: radius.sm,
colors: {
primary: 'rgb(52, 92, 168)',
onPrimary: 'rgb(255, 255, 255)',
primaryContainer: 'rgb(217, 226, 255)',
onPrimaryContainer: 'rgb(0, 26, 67)',
secondary: 'rgb(87, 94, 113)',
onSecondary: 'rgb(255, 255, 255)',
secondaryContainer: 'rgb(219, 226, 249)',
onSecondaryContainer: 'rgb(20, 27, 44)',
tertiary: 'rgb(114, 85, 115)',
onTertiary: 'rgb(255, 255, 255)',
tertiaryContainer: 'rgb(252, 215, 251)',
onTertiaryContainer: 'rgb(42, 19, 45)',
error: 'rgb(186, 26, 26)',
onError: 'rgb(255, 255, 255)',
errorContainer: 'rgb(255, 218, 214)',
onErrorContainer: 'rgb(65, 0, 2)',
background: 'rgb(254, 251, 255)',
onBackground: 'rgb(27, 27, 31)',
surface: 'rgb(254, 251, 255)',
onSurface: 'rgb(27, 27, 31)',
surfaceVariant: 'rgb(225, 226, 236)',
onSurfaceVariant: 'rgb(68, 71, 79)',
outline: 'rgb(117, 119, 128)',
outlineVariant: 'rgb(197, 198, 208)',
shadow: 'rgb(0, 0, 0)',
scrim: 'rgb(0, 0, 0)',
inverseSurface: 'rgb(48, 48, 52)',
inverseOnSurface: 'rgb(242, 240, 244)',
inversePrimary: 'rgb(175, 198, 255)',
elevation: {
level0: 'transparent',
level1: 'rgb(244, 243, 251)',
level2: 'rgb(238, 238, 248)',
level3: 'rgb(232, 234, 245)',
level4: 'rgb(230, 232, 245)',
level5: 'rgb(226, 229, 243)',
},
surfaceDisabled: 'rgba(27, 27, 31, 0.12)',
onSurfaceDisabled: 'rgba(27, 27, 31, 0.38)',
backdrop: 'rgba(46, 48, 56, 0.4)',
},
};
export const styles = StyleSheet.create({
flex: {
flex: 1,
padding: spacing.sm,
},
flexCenter: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: spacing.sm,
},
flexJustifyBetween: {
flex: 1,
justifyContent: 'space-between',
alignItems: 'center',
padding: spacing.sm,
},
});

@ -0,0 +1,55 @@
import {
createContext,
Dispatch,
ReactNode,
useContext,
useReducer,
} from "react";
type State = { count: number };
type Action = { type: "INCREMENT" | "DECREMENT" };
const initialState: State = { count: 0 };
function counterReducer(state: State, action: Action): State {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
default:
throw new Error("Unhandled action");
}
}
const CounterStateContext = createContext<State | undefined>(undefined);
const CounterDispatchContext = createContext<Dispatch<Action> | undefined>(
undefined
);
export function CounterProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(counterReducer, initialState);
return (
<CounterStateContext.Provider value={state}>
<CounterDispatchContext.Provider value={dispatch}>
{children}
</CounterDispatchContext.Provider>
</CounterStateContext.Provider>
);
}
export function useCounterState() {
const context = useContext(CounterStateContext);
if (context === undefined)
throw new Error("useCounterState must be used within a CounterProvider");
return context;
}
export function useCounterDispatch() {
const context = useContext(CounterDispatchContext);
if (context === undefined)
throw new Error("useCounterDispatch must be used within a CounterProvider");
return context;
}

@ -0,0 +1,6 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true
}
}
Loading…
Cancel
Save