Peace 2 weeks ago
parent cd5b8db8db
commit 2ba5602b27
  1. 70
      lite.todo/app/(tabs)/_layout.tsx
  2. 28
      lite.todo/app/(tabs)/done.tsx
  3. 41
      lite.todo/app/(tabs)/index.tsx
  4. 2
      lite.todo/app/_layout.tsx
  5. 59
      lite.todo/components/TodoList.tsx
  6. 1600
      lite.todo/components/ui/icon/index.tsx
  7. 1573
      lite.todo/components/ui/icon/index.web.tsx
  8. 26
      lite.todo/package-lock.json
  9. 14
      lite.todo/package.json
  10. 23
      lite.todo/store/todoProvider.tsx

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

@ -1,5 +1,31 @@
import TodoList from "@/components/TodoList";
import { Button, ButtonText } from "@/components/ui/button";
import { GS } from "@/components/ui/gluestack-ui-provider/gluestack-ui-token";
import { HStack } from "@/components/ui/hstack";
import { Text } from "@/components/ui/text";
import { VStack } from "@/components/ui/vstack";
import { useTodosCtx } from "@/store/todoProvider";
import { SafeAreaView } from "react-native-safe-area-context";
export default function DoneTab() {
return <SafeAreaView className="flex-1"></SafeAreaView>;
const { doneTodos, toggle, remove, clearDone } = useTodosCtx();
return (
<SafeAreaView className="flex-1 bg-white dark:bg-neutral-900">
<VStack className="flex-1 px-4 py-1 gap-3">
<Text className="text-2xl font-bold"> </Text>
<TodoList data={doneTodos} onToggle={toggle} onRemove={remove} />
<HStack className="justify-end">
<Button
className="flex-1"
variant={GS.variant.outline}
action={GS.action.secondary}
onPress={clearDone}
>
<ButtonText> </ButtonText>
</Button>
</HStack>
</VStack>
</SafeAreaView>
);
}

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

@ -11,7 +11,7 @@ export default function App() {
<GluestackUIProvider mode="light">
<SafeAreaView style={{ flex: 1 }}>
<StatusBar style="auto" />
<Stack screenOptions={{ headerShown: false }} />
<Stack screenOptions={{ header: () => null }} />
</SafeAreaView>
</GluestackUIProvider>
);

@ -11,6 +11,7 @@ import {
} from "./ui/checkbox";
import { Button, ButtonText } from "./ui/button";
import { GS } from "./ui/gluestack-ui-provider/gluestack-ui-token";
import { CheckIcon, CloseIcon, Icon } from "@/components/ui/icon";
type Props = {
data: Todo[];
@ -24,31 +25,41 @@ export default function TodoList({ data, onToggle, onRemove }: Props) {
data={data}
keyExtractor={(it) => it.id}
contentContainerStyle={{ paddingBottom: 80 }}
renderItem={({ item }) => (
<HStack className="items-center py-2">
<Checkbox
isChecked={item.done}
onChange={(_isChecked) => onToggle(item.id)}
value={item.title}
>
<CheckboxIndicator>
<CheckboxIcon />
</CheckboxIndicator>
<CheckboxLabel className="data-[checked=true]:line-through">
{item.title}
</CheckboxLabel>
</Checkbox>
{onRemove ? (
<Button
variant={GS.variant.link}
action={GS.action.negative}
onPress={() => onRemove(item.id)}
renderItem={({ item }) => {
return (
<HStack className="items-center justify-between py-2 hover:bg-secondary-200">
<Checkbox
isChecked={item.done}
onChange={(_isChecked) => onToggle(item.id)}
value={item.title}
>
<ButtonText></ButtonText>
</Button>
) : null}
</HStack>
)}
<CheckboxIndicator>
<CheckboxIcon as={CheckIcon} />
</CheckboxIndicator>
<CheckboxLabel className="data-[checked=true]:line-through">
{item.title}
</CheckboxLabel>
</Checkbox>
{onRemove ? (
<Button
className="pl-2"
variant={GS.variant.link}
action={GS.action.secondary}
onPress={() => onRemove(item.id)}
>
<HStack className="flex justify-center items-center">
<Icon
as={CloseIcon}
size={GS.size.xs}
className="text-secondary-950"
/>
<ButtonText size={GS.size.xs}></ButtonText>
</HStack>
</Button>
) : null}
</HStack>
);
}}
ListEmptyComponent={
<Text className="text-neutral-400 mt-6 text-center">
.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -29,7 +29,7 @@
"react-native-reanimated": "^4.1.0",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "~4.11.1",
"react-native-svg": "^15.2.0",
"react-native-svg": "^15.12.0",
"react-native-worklets": "^0.5.0",
"react-stately": "^3.39.0",
"tailwind-variants": "^0.1.20",
@ -2096,21 +2096,6 @@
"react-native-web": ">=0.19.0"
}
},
"node_modules/@gluestack-ui/core/node_modules/react-native-svg": {
"version": "15.12.1",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.12.1.tgz",
"integrity": "sha512-vCuZJDf8a5aNC2dlMovEv4Z0jjEUET53lm/iILFnFewa15b4atjVxU6Wirm6O9y6dEsdjDZVD7Q3QM4T1wlI8g==",
"license": "MIT",
"dependencies": {
"css-select": "^5.1.0",
"css-tree": "^1.1.3",
"warn-once": "0.1.1"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/@gluestack-ui/utils": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@gluestack-ui/utils/-/utils-3.0.0.tgz",
@ -9750,13 +9735,14 @@
}
},
"node_modules/react-native-svg": {
"version": "15.2.0",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.2.0.tgz",
"integrity": "sha512-R0E6IhcJfVLsL0lRmnUSm72QO+mTqcAOM5Jb8FVGxJqX3NfJMlMP0YyvcajZiaRR8CqQUpEoqrY25eyZb006kw==",
"version": "15.12.0",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.12.0.tgz",
"integrity": "sha512-iE25PxIJ6V0C6krReLquVw6R0QTsRTmEQc4K2Co3P6zsimU/jltcDBKYDy1h/5j9S/fqmMeXnpM+9LEWKJKI6A==",
"license": "MIT",
"dependencies": {
"css-select": "^5.1.0",
"css-tree": "^1.1.3"
"css-tree": "^1.1.3",
"warn-once": "0.1.1"
},
"peerDependencies": {
"react": "*",

@ -10,12 +10,16 @@
},
"dependencies": {
"@expo/html-elements": "^0.10.1",
"@expo/vector-icons": "^14.1.0",
"@gluestack-ui/core": "^3.0.0",
"@gluestack-ui/utils": "^3.0.0",
"@legendapp/motion": "^2.3.0",
"@react-native-async-storage/async-storage": "^2.2.0",
"babel-plugin-module-resolver": "^5.0.2",
"expo": "~53.0.22",
"expo-constants": "~17.1.7",
"expo-linking": "~7.1.7",
"expo-router": "~5.1.5",
"expo-status-bar": "~2.2.3",
"lucide-react-native": "^0.510.0",
"nativewind": "^4.1.23",
@ -25,16 +29,12 @@
"react-native": "0.79.6",
"react-native-reanimated": "^4.1.0",
"react-native-safe-area-context": "5.4.0",
"react-native-svg": "^15.2.0",
"react-native-screens": "~4.11.1",
"react-native-svg": "^15.12.0",
"react-native-worklets": "^0.5.0",
"react-stately": "^3.39.0",
"tailwind-variants": "^0.1.20",
"tailwindcss": "^3.4.17",
"expo-router": "~5.1.5",
"react-native-screens": "~4.11.1",
"expo-linking": "~7.1.7",
"expo-constants": "~17.1.7",
"@expo/vector-icons": "^14.1.0"
"tailwindcss": "^3.4.17"
},
"devDependencies": {
"@babel/core": "^7.25.2",

@ -0,0 +1,23 @@
import { createContext, useContext } from "react";
import { type Todo, useTodos } from "./useTodos";
type TodosContextValue = ReturnType<typeof useTodos>;
const TodosContext = createContext<TodosContextValue | null>(null);
export function TodosProvider({ children }: { children: React.ReactNode }) {
const store = useTodos();
return (
<TodosContext.Provider value={store}>{children}</TodosContext.Provider>
);
}
export function useTodosCtx(): TodosContextValue {
const ctx = useContext(TodosContext);
if (!ctx) {
throw new Error("use TodosCtx must be used within <TodosProvider>");
}
return ctx;
}
export type { Todo };
Loading…
Cancel
Save