Peace 2 weeks ago
parent 2ba5602b27
commit 8e145cc62e
  1. 71
      lite.todo/app/(tabs)/_layout.tsx
  2. 6
      lite.todo/app/(tabs)/done.tsx
  3. 66
      lite.todo/app/(tabs)/index.tsx
  4. 15
      lite.todo/app/_layout.tsx
  5. 22
      lite.todo/components/todo-list.tsx
  6. 27
      lite.todo/package-lock.json
  7. 1
      lite.todo/package.json
  8. 1
      lite.todo/store/todoProvider.tsx

@ -1,45 +1,42 @@
import { Tabs } from "expo-router"; import { Tabs } from "expo-router";
import { MaterialIcons } from "@expo/vector-icons"; import { MaterialIcons } from "@expo/vector-icons";
import { TodosProvider } from "@/store/todoProvider";
export default function TabLayout() { export default function TabLayout() {
return ( return (
<TodosProvider> <Tabs
<Tabs screenOptions={{
screenOptions={{ headerShown: false,
headerShown: false, tabBarStyle: { height: 56 },
tabBarStyle: { height: 56 }, tabBarLabelStyle: { fontSize: 12 },
tabBarLabelStyle: { fontSize: 12 }, tabBarHideOnKeyboard: false,
tabBarHideOnKeyboard: true, }}
>
<Tabs.Screen
name="index"
options={{
title: "할 일",
tabBarIcon: ({ color, focused, size }) => (
<MaterialIcons
name={focused ? "check-box" : "check-box-outline-blank"}
size={size ?? 24}
color={color}
/>
),
}} }}
> />
<Tabs.Screen <Tabs.Screen
name="index" name="done"
options={{ options={{
title: "할 일", title: "완료됨",
tabBarIcon: ({ color, focused, size }) => ( tabBarIcon: ({ color, focused, size }) => (
<MaterialIcons <MaterialIcons
name={focused ? "check-box" : "check-box-outline-blank"} name={focused ? "done-all" : "done"}
size={size ?? 24} size={size ?? 24}
color={color} color={color}
/> />
), ),
}} }}
/> />
<Tabs.Screen </Tabs>
name="done"
options={{
title: "완료됨",
tabBarIcon: ({ color, focused, size }) => (
<MaterialIcons
name={focused ? "done-all" : "done"}
size={size ?? 24}
color={color}
/>
),
}}
/>
</Tabs>
</TodosProvider>
); );
} }

@ -1,4 +1,4 @@
import TodoList from "@/components/TodoList"; import TodoList from "@/components/todo-list";
import { Button, ButtonText } from "@/components/ui/button"; import { Button, ButtonText } from "@/components/ui/button";
import { GS } from "@/components/ui/gluestack-ui-provider/gluestack-ui-token"; import { GS } from "@/components/ui/gluestack-ui-provider/gluestack-ui-token";
import { HStack } from "@/components/ui/hstack"; import { HStack } from "@/components/ui/hstack";
@ -11,8 +11,8 @@ export default function DoneTab() {
const { doneTodos, toggle, remove, clearDone } = useTodosCtx(); const { doneTodos, toggle, remove, clearDone } = useTodosCtx();
return ( return (
<SafeAreaView className="flex-1 bg-white dark:bg-neutral-900"> <SafeAreaView className="flex-1 px-4 py-1 bg-white dark:bg-neutral-900">
<VStack className="flex-1 px-4 py-1 gap-3"> <VStack className="flex-1 gap-3">
<Text className="text-2xl font-bold"> </Text> <Text className="text-2xl font-bold"> </Text>
<TodoList data={doneTodos} onToggle={toggle} onRemove={remove} /> <TodoList data={doneTodos} onToggle={toggle} onRemove={remove} />
<HStack className="justify-end"> <HStack className="justify-end">

@ -1,4 +1,4 @@
import TodoList from "@/components/TodoList"; import TodoList from "@/components/todo-list";
import { Button, ButtonText } from "@/components/ui/button"; import { Button, ButtonText } from "@/components/ui/button";
import { GS } from "@/components/ui/gluestack-ui-provider/gluestack-ui-token"; import { GS } from "@/components/ui/gluestack-ui-provider/gluestack-ui-token";
import { HStack } from "@/components/ui/hstack"; import { HStack } from "@/components/ui/hstack";
@ -7,11 +7,12 @@ import { Text } from "@/components/ui/text";
import { VStack } from "@/components/ui/vstack"; import { VStack } from "@/components/ui/vstack";
import { useTodosCtx } from "@/store/todoProvider"; import { useTodosCtx } from "@/store/todoProvider";
import { useState } from "react"; import { useState } from "react";
import { KeyboardAvoidingView, Platform } from "react-native"; import { KeyboardAvoidingView, Platform, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context"; import { SafeAreaView } from "react-native-safe-area-context";
export default function ActiveTab() { export default function ActiveTab() {
const { activeTodos, leftCount, add, toggle, completeAll } = useTodosCtx(); const { todos, activeTodos, leftCount, add, toggle, completeAll, clearDone } =
useTodosCtx();
const [title, setTitle] = useState(""); const [title, setTitle] = useState("");
const onAdd = () => { const onAdd = () => {
@ -20,33 +21,38 @@ export default function ActiveTab() {
}; };
return ( return (
<SafeAreaView style={{ flex: 1 }} className="bg-white dark:bg-neutral-900"> <SafeAreaView className="flex-1 px-4 py-1 bg-white dark:bg-neutral-900">
<KeyboardAvoidingView <VStack className="flex-1 gap-3">
style={{ flex: 1 }} <HStack className="flex justify-between">
behavior={Platform.OS === "ios" ? "padding" : "height"} <Text className="text-2xl font-bold"> </Text>
keyboardVerticalOffset={30} </HStack>
>
<VStack className="flex-1 px-4 py-1 gap-3"> {/* 입력 창 */}
<Text className="text-2xl font-bold"></Text> <HStack className="gap-2">
{/* 확인 창 */} <Input className="flex-1">
<TodoList data={activeTodos} onToggle={toggle} /> <InputField
{/* 입력 창 */} value={title}
<HStack className="gap-2"> onChangeText={setTitle}
<Input className="flex-1"> placeholder="할 일을 입력하세요"
<InputField returnKeyType="done"
value={title} onSubmitEditing={onAdd}
onChangeText={setTitle} />
placeholder="할 일을 입력하세요" </Input>
returnKeyType="done" <Button action={GS.action.primary} onPress={onAdd}>
onSubmitEditing={onAdd} <ButtonText></ButtonText>
/> </Button>
</Input> </HStack>
<Button action={GS.action.primary} onPress={onAdd}>
<ButtonText></ButtonText> {/* 확인 창 */}
</Button> <TodoList data={todos} onToggle={toggle} />
</HStack> <Button
</VStack> variant={GS.variant.outline}
</KeyboardAvoidingView> action={GS.action.secondary}
onPress={clearDone}
>
<ButtonText> </ButtonText>
</Button>
</VStack>
</SafeAreaView> </SafeAreaView>
); );
} }

@ -5,15 +5,18 @@ import { Stack } from "expo-router";
import { GluestackUIProvider } from "@/components/ui/gluestack-ui-provider"; import { GluestackUIProvider } from "@/components/ui/gluestack-ui-provider";
import "@/global.css"; import "@/global.css";
import { TodosProvider } from "@/store/todoProvider";
export default function App() { export default function App() {
return ( return (
<GluestackUIProvider mode="light"> <TodosProvider>
<SafeAreaView style={{ flex: 1 }}> <GluestackUIProvider mode="light">
<StatusBar style="auto" /> <SafeAreaView style={{ flex: 1 }}>
<Stack screenOptions={{ header: () => null }} /> <StatusBar style="auto" />
</SafeAreaView> <Stack screenOptions={{ header: () => null }} />
</GluestackUIProvider> </SafeAreaView>
</GluestackUIProvider>
</TodosProvider>
); );
} }

@ -12,16 +12,24 @@ import {
import { Button, ButtonText } from "./ui/button"; import { Button, ButtonText } from "./ui/button";
import { GS } from "./ui/gluestack-ui-provider/gluestack-ui-token"; import { GS } from "./ui/gluestack-ui-provider/gluestack-ui-token";
import { CheckIcon, CloseIcon, Icon } from "@/components/ui/icon"; import { CheckIcon, CloseIcon, Icon } from "@/components/ui/icon";
import * as Animatable from "react-native-animatable";
type Props = { type Props = {
data: Todo[]; data: Todo[];
onToggle: (id: string) => void; onToggle: (id: string) => void;
onRemove?: (id: string) => void; onRemove?: (id: string) => void;
className?: string;
}; };
export default function TodoList({ data, onToggle, onRemove }: Props) { export default function TodoList({
data,
onToggle,
onRemove,
className,
}: Props) {
return ( return (
<FlatList <FlatList
className={className}
data={data} data={data}
keyExtractor={(it) => it.id} keyExtractor={(it) => it.id}
contentContainerStyle={{ paddingBottom: 80 }} contentContainerStyle={{ paddingBottom: 80 }}
@ -33,9 +41,15 @@ export default function TodoList({ data, onToggle, onRemove }: Props) {
onChange={(_isChecked) => onToggle(item.id)} onChange={(_isChecked) => onToggle(item.id)}
value={item.title} value={item.title}
> >
<CheckboxIndicator> <Animatable.View
<CheckboxIcon as={CheckIcon} /> animation={item.done ? "bounce" : undefined}
</CheckboxIndicator> duration={300}
useNativeDriver
>
<CheckboxIndicator>
<CheckboxIcon as={CheckIcon} />
</CheckboxIndicator>
</Animatable.View>
<CheckboxLabel className="data-[checked=true]:line-through"> <CheckboxLabel className="data-[checked=true]:line-through">
{item.title} {item.title}
</CheckboxLabel> </CheckboxLabel>

@ -26,6 +26,7 @@
"react-aria": "^3.33.0", "react-aria": "^3.33.0",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-native": "0.79.6", "react-native": "0.79.6",
"react-native-animatable": "^1.4.0",
"react-native-reanimated": "^4.1.0", "react-native-reanimated": "^4.1.0",
"react-native-safe-area-context": "5.4.0", "react-native-safe-area-context": "5.4.0",
"react-native-screens": "~4.11.1", "react-native-screens": "~4.11.1",
@ -9340,6 +9341,23 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/prop-types/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@ -9618,6 +9636,15 @@
} }
} }
}, },
"node_modules/react-native-animatable": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/react-native-animatable/-/react-native-animatable-1.4.0.tgz",
"integrity": "sha512-DZwaDVWm2NBvBxf7I0wXKXLKb/TxDnkV53sWhCvei1pRyTX3MVFpkvdYBknNBqPrxYuAIlPxEp7gJOidIauUkw==",
"license": "MIT",
"dependencies": {
"prop-types": "^15.8.1"
}
},
"node_modules/react-native-css-interop": { "node_modules/react-native-css-interop": {
"version": "0.1.22", "version": "0.1.22",
"resolved": "https://registry.npmjs.org/react-native-css-interop/-/react-native-css-interop-0.1.22.tgz", "resolved": "https://registry.npmjs.org/react-native-css-interop/-/react-native-css-interop-0.1.22.tgz",

@ -27,6 +27,7 @@
"react-aria": "^3.33.0", "react-aria": "^3.33.0",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-native": "0.79.6", "react-native": "0.79.6",
"react-native-animatable": "^1.4.0",
"react-native-reanimated": "^4.1.0", "react-native-reanimated": "^4.1.0",
"react-native-safe-area-context": "5.4.0", "react-native-safe-area-context": "5.4.0",
"react-native-screens": "~4.11.1", "react-native-screens": "~4.11.1",

@ -6,6 +6,7 @@ type TodosContextValue = ReturnType<typeof useTodos>;
const TodosContext = createContext<TodosContextValue | null>(null); const TodosContext = createContext<TodosContextValue | null>(null);
export function TodosProvider({ children }: { children: React.ReactNode }) { export function TodosProvider({ children }: { children: React.ReactNode }) {
console.log("[TODO_PROVIDER] MOUNTED!");
const store = useTodos(); const store = useTodos();
return ( return (
<TodosContext.Provider value={store}>{children}</TodosContext.Provider> <TodosContext.Provider value={store}>{children}</TodosContext.Provider>

Loading…
Cancel
Save