diff --git a/lite.todo/App.tsx b/lite.todo/App.tsx
deleted file mode 100755
index 0d3aad6..0000000
--- a/lite.todo/App.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { StatusBar } from 'expo-status-bar';
-import { StyleSheet, Text, View } from 'react-native';
-
-import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider';
-import '@/global.css';
-
-export default function App() {
- return (
-
-
-
- Open up App.tsx to start working on your app!
-
-
-
-
- );
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#fff',
- alignItems: 'center',
- justifyContent: 'center',
- },
-});
diff --git a/lite.todo/App_backup.tsx b/lite.todo/App_backup.tsx
new file mode 100755
index 0000000..ff2f37f
--- /dev/null
+++ b/lite.todo/App_backup.tsx
@@ -0,0 +1,25 @@
+import { StatusBar } from "expo-status-bar";
+import { StyleSheet, Text, View } from "react-native";
+
+import { GluestackUIProvider } from "@/components/ui/gluestack-ui-provider";
+import "@/global.css";
+
+export default function App() {
+ return (
+
+
+ Open up App.tsx to start working on your app!
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: "#fff",
+ alignItems: "center",
+ justifyContent: "center",
+ },
+});
diff --git a/lite.todo/app.json b/lite.todo/app.json
index 5fb0302..7322b0f 100755
--- a/lite.todo/app.json
+++ b/lite.todo/app.json
@@ -24,6 +24,8 @@
},
"web": {
"favicon": "./assets/favicon.png"
- }
+ },
+ "plugins": ["expo-router"],
+ "scheme": "lite-todo-scheme"
}
}
diff --git a/lite.todo/app/(tabs)/_layout.tsx b/lite.todo/app/(tabs)/_layout.tsx
new file mode 100755
index 0000000..1045818
--- /dev/null
+++ b/lite.todo/app/(tabs)/_layout.tsx
@@ -0,0 +1,41 @@
+import { Tabs } from "expo-router";
+import { MaterialIcons } from "@expo/vector-icons";
+
+export default function TabLayout() {
+ return (
+
+ (
+
+ ),
+ }}
+ />
+ (
+
+ ),
+ }}
+ />
+
+ );
+}
diff --git a/lite.todo/app/(tabs)/done.tsx b/lite.todo/app/(tabs)/done.tsx
new file mode 100755
index 0000000..9271cb2
--- /dev/null
+++ b/lite.todo/app/(tabs)/done.tsx
@@ -0,0 +1,5 @@
+import { SafeAreaView } from "react-native-safe-area-context";
+
+export default function DoneTab() {
+ return ;
+}
diff --git a/lite.todo/app/(tabs)/index.tsx b/lite.todo/app/(tabs)/index.tsx
new file mode 100755
index 0000000..f298853
--- /dev/null
+++ b/lite.todo/app/(tabs)/index.tsx
@@ -0,0 +1,5 @@
+import { SafeAreaView } from "react-native-safe-area-context";
+
+export default function ActiveTab() {
+ return ;
+}
diff --git a/lite.todo/app/_layout.tsx b/lite.todo/app/_layout.tsx
new file mode 100755
index 0000000..73aae1c
--- /dev/null
+++ b/lite.todo/app/_layout.tsx
@@ -0,0 +1,27 @@
+import { StatusBar } from "expo-status-bar";
+import { StyleSheet, Text, View } from "react-native";
+import { SafeAreaView } from "react-native-safe-area-context";
+import { Stack } from "expo-router";
+
+import { GluestackUIProvider } from "@/components/ui/gluestack-ui-provider";
+import "@/global.css";
+
+export default function App() {
+ return (
+
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: "#fff",
+ // alignItems: "center",
+ // justifyContent: "center",
+ },
+});
diff --git a/lite.todo/components/TodoList.tsx b/lite.todo/components/TodoList.tsx
new file mode 100755
index 0000000..2c9dffc
--- /dev/null
+++ b/lite.todo/components/TodoList.tsx
@@ -0,0 +1,59 @@
+import { Todo } from "@/store/useTodos";
+import { FlatList } from "react-native";
+import { VStack } from "./ui/vstack";
+import { Text } from "./ui/text";
+import { HStack } from "./ui/hstack";
+import {
+ Checkbox,
+ CheckboxIcon,
+ CheckboxIndicator,
+ CheckboxLabel,
+} from "./ui/checkbox";
+import { Button, ButtonText } from "./ui/button";
+import { GS } from "./ui/gluestack-ui-provider/gluestack-ui-token";
+
+type Props = {
+ data: Todo[];
+ onToggle: (id: string) => void;
+ onRemove?: (id: string) => void;
+};
+
+export default function TodoList({ data, onToggle, onRemove }: Props) {
+ return (
+ it.id}
+ contentContainerStyle={{ paddingBottom: 80 }}
+ renderItem={({ item }) => (
+
+ onToggle(item.id)}
+ value={item.title}
+ >
+
+
+
+
+ {item.title}
+
+
+ {onRemove ? (
+
+ ) : null}
+
+ )}
+ ListEmptyComponent={
+
+ 할 일이 없습니다.
+
+ }
+ />
+ );
+}
diff --git a/lite.todo/components/ui/button/index.tsx b/lite.todo/components/ui/button/index.tsx
new file mode 100755
index 0000000..0310e63
--- /dev/null
+++ b/lite.todo/components/ui/button/index.tsx
@@ -0,0 +1,439 @@
+'use client';
+import React from 'react';
+import { createButton } from '@gluestack-ui/core/button/creator';
+import { tva } from '@gluestack-ui/utils/nativewind-utils';
+import {
+ withStyleContext,
+ useStyleContext,
+} from '@gluestack-ui/utils/nativewind-utils';
+import { cssInterop } from 'nativewind';
+import { ActivityIndicator, Pressable, Text, View } from 'react-native';
+import type { VariantProps } from 'tailwind-variants';
+import { PrimitiveIcon, UIIcon } from '@gluestack-ui/core/icon/creator';
+
+const SCOPE = 'BUTTON';
+
+const Root = withStyleContext(Pressable, SCOPE);
+
+const UIButton = createButton({
+ Root: Root,
+ Text,
+ Group: View,
+ Spinner: ActivityIndicator,
+ Icon: UIIcon,
+});
+
+cssInterop(PrimitiveIcon, {
+ className: {
+ target: 'style',
+ nativeStyleToProp: {
+ height: true,
+ width: true,
+ fill: true,
+ color: 'classNameColor',
+ stroke: true,
+ },
+ },
+});
+
+const buttonStyle = tva({
+ base: 'group/button rounded bg-primary-500 flex-row items-center justify-center data-[focus-visible=true]:web:outline-none data-[focus-visible=true]:web:ring-2 data-[disabled=true]:opacity-40 gap-2',
+ variants: {
+ action: {
+ primary:
+ 'bg-primary-500 data-[hover=true]:bg-primary-600 data-[active=true]:bg-primary-700 border-primary-300 data-[hover=true]:border-primary-400 data-[active=true]:border-primary-500 data-[focus-visible=true]:web:ring-indicator-info',
+ secondary:
+ 'bg-secondary-500 border-secondary-300 data-[hover=true]:bg-secondary-600 data-[hover=true]:border-secondary-400 data-[active=true]:bg-secondary-700 data-[active=true]:border-secondary-700 data-[focus-visible=true]:web:ring-indicator-info',
+ positive:
+ 'bg-success-500 border-success-300 data-[hover=true]:bg-success-600 data-[hover=true]:border-success-400 data-[active=true]:bg-success-700 data-[active=true]:border-success-500 data-[focus-visible=true]:web:ring-indicator-info',
+ negative:
+ 'bg-error-500 border-error-300 data-[hover=true]:bg-error-600 data-[hover=true]:border-error-400 data-[active=true]:bg-error-700 data-[active=true]:border-error-500 data-[focus-visible=true]:web:ring-indicator-info',
+ default:
+ 'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
+ },
+ variant: {
+ link: 'px-0',
+ outline:
+ 'bg-transparent border data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
+ solid: '',
+ },
+
+ size: {
+ xs: 'px-3.5 h-8',
+ sm: 'px-4 h-9',
+ md: 'px-5 h-10',
+ lg: 'px-6 h-11',
+ xl: 'px-7 h-12',
+ },
+ },
+ compoundVariants: [
+ {
+ action: 'primary',
+ variant: 'link',
+ class:
+ 'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
+ },
+ {
+ action: 'secondary',
+ variant: 'link',
+ class:
+ 'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
+ },
+ {
+ action: 'positive',
+ variant: 'link',
+ class:
+ 'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
+ },
+ {
+ action: 'negative',
+ variant: 'link',
+ class:
+ 'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
+ },
+ {
+ action: 'primary',
+ variant: 'outline',
+ class:
+ 'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
+ },
+ {
+ action: 'secondary',
+ variant: 'outline',
+ class:
+ 'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
+ },
+ {
+ action: 'positive',
+ variant: 'outline',
+ class:
+ 'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
+ },
+ {
+ action: 'negative',
+ variant: 'outline',
+ class:
+ 'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
+ },
+ ],
+});
+
+const buttonTextStyle = tva({
+ base: 'text-typography-0 font-semibold web:select-none',
+ parentVariants: {
+ action: {
+ primary:
+ 'text-primary-600 data-[hover=true]:text-primary-600 data-[active=true]:text-primary-700',
+ secondary:
+ 'text-typography-500 data-[hover=true]:text-typography-600 data-[active=true]:text-typography-700',
+ positive:
+ 'text-success-600 data-[hover=true]:text-success-600 data-[active=true]:text-success-700',
+ negative:
+ 'text-error-600 data-[hover=true]:text-error-600 data-[active=true]:text-error-700',
+ },
+ variant: {
+ link: 'data-[hover=true]:underline data-[active=true]:underline',
+ outline: '',
+ solid:
+ 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
+ },
+ size: {
+ xs: 'text-xs',
+ sm: 'text-sm',
+ md: 'text-base',
+ lg: 'text-lg',
+ xl: 'text-xl',
+ },
+ },
+ parentCompoundVariants: [
+ {
+ variant: 'solid',
+ action: 'primary',
+ class:
+ 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
+ },
+ {
+ variant: 'solid',
+ action: 'secondary',
+ class:
+ 'text-typography-800 data-[hover=true]:text-typography-800 data-[active=true]:text-typography-800',
+ },
+ {
+ variant: 'solid',
+ action: 'positive',
+ class:
+ 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
+ },
+ {
+ variant: 'solid',
+ action: 'negative',
+ class:
+ 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
+ },
+ {
+ variant: 'outline',
+ action: 'primary',
+ class:
+ 'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500',
+ },
+ {
+ variant: 'outline',
+ action: 'secondary',
+ class:
+ 'text-typography-500 data-[hover=true]:text-primary-600 data-[active=true]:text-typography-700',
+ },
+ {
+ variant: 'outline',
+ action: 'positive',
+ class:
+ 'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500',
+ },
+ {
+ variant: 'outline',
+ action: 'negative',
+ class:
+ 'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500',
+ },
+ ],
+});
+
+const buttonIconStyle = tva({
+ base: 'fill-none',
+ parentVariants: {
+ variant: {
+ link: 'data-[hover=true]:underline data-[active=true]:underline',
+ outline: '',
+ solid:
+ 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
+ },
+ size: {
+ xs: 'h-3.5 w-3.5',
+ sm: 'h-4 w-4',
+ md: 'h-[18px] w-[18px]',
+ lg: 'h-[18px] w-[18px]',
+ xl: 'h-5 w-5',
+ },
+ action: {
+ primary:
+ 'text-primary-600 data-[hover=true]:text-primary-600 data-[active=true]:text-primary-700',
+ secondary:
+ 'text-typography-500 data-[hover=true]:text-typography-600 data-[active=true]:text-typography-700',
+ positive:
+ 'text-success-600 data-[hover=true]:text-success-600 data-[active=true]:text-success-700',
+
+ negative:
+ 'text-error-600 data-[hover=true]:text-error-600 data-[active=true]:text-error-700',
+ },
+ },
+ parentCompoundVariants: [
+ {
+ variant: 'solid',
+ action: 'primary',
+ class:
+ 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
+ },
+ {
+ variant: 'solid',
+ action: 'secondary',
+ class:
+ 'text-typography-800 data-[hover=true]:text-typography-800 data-[active=true]:text-typography-800',
+ },
+ {
+ variant: 'solid',
+ action: 'positive',
+ class:
+ 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
+ },
+ {
+ variant: 'solid',
+ action: 'negative',
+ class:
+ 'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
+ },
+ ],
+});
+
+const buttonGroupStyle = tva({
+ base: '',
+ variants: {
+ space: {
+ 'xs': 'gap-1',
+ 'sm': 'gap-2',
+ 'md': 'gap-3',
+ 'lg': 'gap-4',
+ 'xl': 'gap-5',
+ '2xl': 'gap-6',
+ '3xl': 'gap-7',
+ '4xl': 'gap-8',
+ },
+ isAttached: {
+ true: 'gap-0',
+ },
+ flexDirection: {
+ 'row': 'flex-row',
+ 'column': 'flex-col',
+ 'row-reverse': 'flex-row-reverse',
+ 'column-reverse': 'flex-col-reverse',
+ },
+ },
+});
+
+type IButtonProps = Omit<
+ React.ComponentPropsWithoutRef,
+ 'context'
+> &
+ VariantProps & { className?: string };
+
+const Button = React.forwardRef<
+ React.ElementRef,
+ IButtonProps
+>(
+ (
+ { className, variant = 'solid', size = 'md', action = 'primary', ...props },
+ ref
+ ) => {
+ return (
+
+ );
+ }
+);
+
+type IButtonTextProps = React.ComponentPropsWithoutRef &
+ VariantProps & { className?: string };
+
+const ButtonText = React.forwardRef<
+ React.ElementRef,
+ IButtonTextProps
+>(({ className, variant, size, action, ...props }, ref) => {
+ const {
+ variant: parentVariant,
+ size: parentSize,
+ action: parentAction,
+ } = useStyleContext(SCOPE);
+
+ return (
+
+ );
+});
+
+const ButtonSpinner = UIButton.Spinner;
+
+type IButtonIcon = React.ComponentPropsWithoutRef &
+ VariantProps & {
+ className?: string | undefined;
+ as?: React.ElementType;
+ height?: number;
+ width?: number;
+ };
+
+const ButtonIcon = React.forwardRef<
+ React.ElementRef,
+ IButtonIcon
+>(({ className, size, ...props }, ref) => {
+ const {
+ variant: parentVariant,
+ size: parentSize,
+ action: parentAction,
+ } = useStyleContext(SCOPE);
+
+ if (typeof size === 'number') {
+ return (
+
+ );
+ } else if (
+ (props.height !== undefined || props.width !== undefined) &&
+ size === undefined
+ ) {
+ return (
+
+ );
+ }
+ return (
+
+ );
+});
+
+type IButtonGroupProps = React.ComponentPropsWithoutRef &
+ VariantProps;
+
+const ButtonGroup = React.forwardRef<
+ React.ElementRef,
+ IButtonGroupProps
+>(
+ (
+ {
+ className,
+ space = 'md',
+ isAttached = false,
+ flexDirection = 'column',
+ ...props
+ },
+ ref
+ ) => {
+ return (
+
+ );
+ }
+);
+
+Button.displayName = 'Button';
+ButtonText.displayName = 'ButtonText';
+ButtonSpinner.displayName = 'ButtonSpinner';
+ButtonIcon.displayName = 'ButtonIcon';
+ButtonGroup.displayName = 'ButtonGroup';
+
+export { Button, ButtonText, ButtonSpinner, ButtonIcon, ButtonGroup };
diff --git a/lite.todo/components/ui/checkbox/index.tsx b/lite.todo/components/ui/checkbox/index.tsx
new file mode 100755
index 0000000..27b03d5
--- /dev/null
+++ b/lite.todo/components/ui/checkbox/index.tsx
@@ -0,0 +1,242 @@
+'use client';
+import React from 'react';
+import { createCheckbox } from '@gluestack-ui/core/checkbox/creator';
+import { View, Pressable, Text, Platform } from 'react-native';
+import type { TextProps, ViewProps } from 'react-native';
+import { tva } from '@gluestack-ui/utils/nativewind-utils';
+import {
+ PrimitiveIcon,
+ IPrimitiveIcon,
+ UIIcon,
+} from '@gluestack-ui/core/icon/creator';
+import {
+ withStyleContext,
+ useStyleContext,
+} from '@gluestack-ui/utils/nativewind-utils';
+import { cssInterop } from 'nativewind';
+import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
+
+const IndicatorWrapper = React.forwardRef<
+ React.ComponentRef,
+ ViewProps
+>(function IndicatorWrapper({ ...props }, ref) {
+ return ;
+});
+
+const LabelWrapper = React.forwardRef<
+ React.ComponentRef,
+ TextProps
+>(function LabelWrapper({ ...props }, ref) {
+ return ;
+});
+
+const IconWrapper = React.forwardRef<
+ React.ComponentRef,
+ IPrimitiveIcon
+>(function IconWrapper({ ...props }, ref) {
+ return ;
+});
+
+const SCOPE = 'CHECKBOX';
+const UICheckbox = createCheckbox({
+ // @ts-expect-error : internal implementation for r-19/react-native-web
+ Root:
+ Platform.OS === 'web'
+ ? withStyleContext(View, SCOPE)
+ : withStyleContext(Pressable, SCOPE),
+ Group: View,
+ Icon: IconWrapper,
+ Label: LabelWrapper,
+ Indicator: IndicatorWrapper,
+});
+
+cssInterop(PrimitiveIcon, {
+ className: {
+ target: 'style',
+ nativeStyleToProp: {
+ height: true,
+ width: true,
+ fill: true,
+ color: 'classNameColor',
+ stroke: true,
+ },
+ },
+});
+
+const checkboxStyle = tva({
+ base: 'group/checkbox flex-row items-center justify-start web:cursor-pointer data-[disabled=true]:cursor-not-allowed',
+ variants: {
+ size: {
+ lg: 'gap-2',
+ md: 'gap-2',
+ sm: 'gap-1.5',
+ },
+ },
+});
+
+const checkboxIndicatorStyle = tva({
+ base: 'justify-center items-center border-outline-400 bg-transparent rounded web:data-[focus-visible=true]:outline-none web:data-[focus-visible=true]:ring-2 web:data-[focus-visible=true]:ring-indicator-primary data-[checked=true]:bg-primary-600 data-[checked=true]:border-primary-600 data-[hover=true]:data-[checked=false]:border-outline-500 data-[hover=true]:bg-transparent data-[hover=true]:data-[invalid=true]:border-error-700 data-[hover=true]:data-[checked=true]:bg-primary-700 data-[hover=true]:data-[checked=true]:border-primary-700 data-[hover=true]:data-[checked=true]:data-[disabled=true]:border-primary-600 data-[hover=true]:data-[checked=true]:data-[disabled=true]:bg-primary-600 data-[hover=true]:data-[checked=true]:data-[disabled=true]:opacity-40 data-[hover=true]:data-[checked=true]:data-[disabled=true]:data-[invalid=true]:border-error-700 data-[hover=true]:data-[disabled=true]:border-outline-400 data-[hover=true]:data-[disabled=true]:data-[invalid=true]:border-error-700 data-[active=true]:data-[checked=true]:bg-primary-800 data-[active=true]:data-[checked=true]:border-primary-800 data-[invalid=true]:border-error-700 data-[disabled=true]:opacity-40',
+ parentVariants: {
+ size: {
+ lg: 'w-6 h-6 border-[3px]',
+ md: 'w-5 h-5 border-2',
+ sm: 'w-4 h-4 border-2',
+ },
+ },
+});
+
+const checkboxLabelStyle = tva({
+ base: 'text-typography-600 data-[checked=true]:text-typography-900 data-[hover=true]:text-typography-900 data-[hover=true]:data-[checked=true]:text-typography-900 data-[hover=true]:data-[checked=true]:data-[disabled=true]:text-typography-900 data-[hover=true]:data-[disabled=true]:text-typography-400 data-[active=true]:text-typography-900 data-[active=true]:data-[checked=true]:text-typography-900 data-[disabled=true]:opacity-40 web:select-none',
+ parentVariants: {
+ size: {
+ lg: 'text-lg',
+ md: 'text-base',
+ sm: 'text-sm',
+ },
+ },
+});
+
+const checkboxIconStyle = tva({
+ base: 'text-typography-50 fill-none',
+
+ parentVariants: {
+ size: {
+ sm: 'h-3 w-3',
+ md: 'h-4 w-4',
+ lg: 'h-5 w-5',
+ },
+ },
+});
+
+const CheckboxGroup = UICheckbox.Group;
+
+type ICheckboxProps = React.ComponentPropsWithoutRef &
+ VariantProps;
+
+const Checkbox = React.forwardRef<
+ React.ComponentRef,
+ ICheckboxProps
+>(function Checkbox({ className, size = 'md', ...props }, ref) {
+ return (
+
+ );
+});
+
+type ICheckboxIndicatorProps = React.ComponentPropsWithoutRef<
+ typeof UICheckbox.Indicator
+> &
+ VariantProps;
+
+const CheckboxIndicator = React.forwardRef<
+ React.ComponentRef,
+ ICheckboxIndicatorProps
+>(function CheckboxIndicator({ className, ...props }, ref) {
+ const { size: parentSize } = useStyleContext(SCOPE);
+
+ return (
+
+ );
+});
+
+type ICheckboxLabelProps = React.ComponentPropsWithoutRef<
+ typeof UICheckbox.Label
+> &
+ VariantProps;
+const CheckboxLabel = React.forwardRef<
+ React.ComponentRef,
+ ICheckboxLabelProps
+>(function CheckboxLabel({ className, ...props }, ref) {
+ const { size: parentSize } = useStyleContext(SCOPE);
+ return (
+
+ );
+});
+
+type ICheckboxIconProps = React.ComponentPropsWithoutRef<
+ typeof UICheckbox.Icon
+> &
+ VariantProps;
+
+const CheckboxIcon = React.forwardRef<
+ React.ComponentRef,
+ ICheckboxIconProps
+>(function CheckboxIcon({ className, size, ...props }, ref) {
+ const { size: parentSize } = useStyleContext(SCOPE);
+
+ if (typeof size === 'number') {
+ return (
+
+ );
+ } else if (
+ (props.height !== undefined || props.width !== undefined) &&
+ size === undefined
+ ) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+});
+
+Checkbox.displayName = 'Checkbox';
+CheckboxIndicator.displayName = 'CheckboxIndicator';
+CheckboxLabel.displayName = 'CheckboxLabel';
+CheckboxIcon.displayName = 'CheckboxIcon';
+
+export {
+ Checkbox,
+ CheckboxIndicator,
+ CheckboxLabel,
+ CheckboxIcon,
+ CheckboxGroup,
+};
diff --git a/lite.todo/components/ui/gluestack-ui-provider/gluestack-ui-token.ts b/lite.todo/components/ui/gluestack-ui-provider/gluestack-ui-token.ts
new file mode 100755
index 0000000..1f35084
--- /dev/null
+++ b/lite.todo/components/ui/gluestack-ui-provider/gluestack-ui-token.ts
@@ -0,0 +1,19 @@
+export const GS = {
+ size: {
+ xs: "xs",
+ sm: "sm",
+ md: "md",
+ lg: "lg",
+ },
+ variant: {
+ solid: "solid",
+ outline: "outline",
+ link: "link",
+ },
+ action: {
+ primary: "primary",
+ secondary: "secondary",
+ positive: "positive",
+ negative: "negative",
+ },
+} as const;
diff --git a/lite.todo/components/ui/hstack/index.tsx b/lite.todo/components/ui/hstack/index.tsx
new file mode 100755
index 0000000..725dabb
--- /dev/null
+++ b/lite.todo/components/ui/hstack/index.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
+import { View } from 'react-native';
+import type { ViewProps } from 'react-native';
+import { hstackStyle } from './styles';
+
+type IHStackProps = ViewProps & VariantProps;
+
+const HStack = React.forwardRef, IHStackProps>(
+ function HStack({ className, space, reversed, ...props }, ref) {
+ return (
+
+ );
+ }
+);
+
+HStack.displayName = 'HStack';
+
+export { HStack };
diff --git a/lite.todo/components/ui/hstack/index.web.tsx b/lite.todo/components/ui/hstack/index.web.tsx
new file mode 100755
index 0000000..39f1e40
--- /dev/null
+++ b/lite.todo/components/ui/hstack/index.web.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
+import { hstackStyle } from './styles';
+
+type IHStackProps = React.ComponentPropsWithoutRef<'div'> &
+ VariantProps;
+
+const HStack = React.forwardRef, IHStackProps>(
+ function HStack({ className, space, reversed, ...props }, ref) {
+ return (
+
+ );
+ }
+);
+
+HStack.displayName = 'HStack';
+
+export { HStack };
diff --git a/lite.todo/components/ui/hstack/styles.tsx b/lite.todo/components/ui/hstack/styles.tsx
new file mode 100755
index 0000000..a070e45
--- /dev/null
+++ b/lite.todo/components/ui/hstack/styles.tsx
@@ -0,0 +1,25 @@
+import { isWeb } from '@gluestack-ui/utils/nativewind-utils';
+import { tva } from '@gluestack-ui/utils/nativewind-utils';
+
+const baseStyle = isWeb
+ ? 'flex relative z-0 box-border border-0 list-none min-w-0 min-h-0 bg-transparent items-stretch m-0 p-0 text-decoration-none'
+ : '';
+
+export const hstackStyle = tva({
+ base: `flex-row ${baseStyle}`,
+ variants: {
+ space: {
+ 'xs': 'gap-1',
+ 'sm': 'gap-2',
+ 'md': 'gap-3',
+ 'lg': 'gap-4',
+ 'xl': 'gap-5',
+ '2xl': 'gap-6',
+ '3xl': 'gap-7',
+ '4xl': 'gap-8',
+ },
+ reversed: {
+ true: 'flex-row-reverse',
+ },
+ },
+});
diff --git a/lite.todo/components/ui/input/index.tsx b/lite.todo/components/ui/input/index.tsx
new file mode 100755
index 0000000..c1fbd94
--- /dev/null
+++ b/lite.todo/components/ui/input/index.tsx
@@ -0,0 +1,217 @@
+'use client';
+import React from 'react';
+import { createInput } from '@gluestack-ui/core/input/creator';
+import { View, Pressable, TextInput } from 'react-native';
+import { tva } from '@gluestack-ui/utils/nativewind-utils';
+import {
+ withStyleContext,
+ useStyleContext,
+} from '@gluestack-ui/utils/nativewind-utils';
+import { cssInterop } from 'nativewind';
+import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
+import { PrimitiveIcon, UIIcon } from '@gluestack-ui/core/icon/creator';
+
+const SCOPE = 'INPUT';
+
+const UIInput = createInput({
+ Root: withStyleContext(View, SCOPE),
+ Icon: UIIcon,
+ Slot: Pressable,
+ Input: TextInput,
+});
+
+cssInterop(PrimitiveIcon, {
+ className: {
+ target: 'style',
+ nativeStyleToProp: {
+ height: true,
+ width: true,
+ fill: true,
+ color: 'classNameColor',
+ stroke: true,
+ },
+ },
+});
+
+const inputStyle = tva({
+ base: 'border-background-300 flex-row overflow-hidden content-center data-[hover=true]:border-outline-400 data-[focus=true]:border-primary-700 data-[focus=true]:hover:border-primary-700 data-[disabled=true]:opacity-40 data-[disabled=true]:hover:border-background-300 items-center',
+
+ variants: {
+ size: {
+ xl: 'h-12',
+ lg: 'h-11',
+ md: 'h-10',
+ sm: 'h-9',
+ },
+
+ variant: {
+ underlined:
+ 'rounded-none border-b data-[invalid=true]:border-b-2 data-[invalid=true]:border-error-700 data-[invalid=true]:hover:border-error-700 data-[invalid=true]:data-[focus=true]:border-error-700 data-[invalid=true]:data-[focus=true]:hover:border-error-700 data-[invalid=true]:data-[disabled=true]:hover:border-error-700',
+
+ outline:
+ 'rounded border data-[invalid=true]:border-error-700 data-[invalid=true]:hover:border-error-700 data-[invalid=true]:data-[focus=true]:border-error-700 data-[invalid=true]:data-[focus=true]:hover:border-error-700 data-[invalid=true]:data-[disabled=true]:hover:border-error-700 data-[focus=true]:web:ring-1 data-[focus=true]:web:ring-inset data-[focus=true]:web:ring-indicator-primary data-[invalid=true]:web:ring-1 data-[invalid=true]:web:ring-inset data-[invalid=true]:web:ring-indicator-error data-[invalid=true]:data-[focus=true]:hover:web:ring-1 data-[invalid=true]:data-[focus=true]:hover:web:ring-inset data-[invalid=true]:data-[focus=true]:hover:web:ring-indicator-error data-[invalid=true]:data-[disabled=true]:hover:web:ring-1 data-[invalid=true]:data-[disabled=true]:hover:web:ring-inset data-[invalid=true]:data-[disabled=true]:hover:web:ring-indicator-error',
+
+ rounded:
+ 'rounded-full border data-[invalid=true]:border-error-700 data-[invalid=true]:hover:border-error-700 data-[invalid=true]:data-[focus=true]:border-error-700 data-[invalid=true]:data-[focus=true]:hover:border-error-700 data-[invalid=true]:data-[disabled=true]:hover:border-error-700 data-[focus=true]:web:ring-1 data-[focus=true]:web:ring-inset data-[focus=true]:web:ring-indicator-primary data-[invalid=true]:web:ring-1 data-[invalid=true]:web:ring-inset data-[invalid=true]:web:ring-indicator-error data-[invalid=true]:data-[focus=true]:hover:web:ring-1 data-[invalid=true]:data-[focus=true]:hover:web:ring-inset data-[invalid=true]:data-[focus=true]:hover:web:ring-indicator-error data-[invalid=true]:data-[disabled=true]:hover:web:ring-1 data-[invalid=true]:data-[disabled=true]:hover:web:ring-inset data-[invalid=true]:data-[disabled=true]:hover:web:ring-indicator-error',
+ },
+ },
+});
+
+const inputIconStyle = tva({
+ base: 'justify-center items-center text-typography-400 fill-none',
+ parentVariants: {
+ size: {
+ '2xs': 'h-3 w-3',
+ 'xs': 'h-3.5 w-3.5',
+ 'sm': 'h-4 w-4',
+ 'md': 'h-[18px] w-[18px]',
+ 'lg': 'h-5 w-5',
+ 'xl': 'h-6 w-6',
+ },
+ },
+});
+
+const inputSlotStyle = tva({
+ base: 'justify-center items-center web:disabled:cursor-not-allowed',
+});
+
+const inputFieldStyle = tva({
+ base: 'flex-1 text-typography-900 py-0 px-3 placeholder:text-typography-500 h-full ios:leading-[0px] web:cursor-text web:data-[disabled=true]:cursor-not-allowed',
+
+ parentVariants: {
+ variant: {
+ underlined: 'web:outline-0 web:outline-none px-0',
+ outline: 'web:outline-0 web:outline-none',
+ rounded: 'web:outline-0 web:outline-none px-4',
+ },
+
+ size: {
+ '2xs': 'text-2xs',
+ 'xs': 'text-xs',
+ 'sm': 'text-sm',
+ 'md': 'text-base',
+ 'lg': 'text-lg',
+ 'xl': 'text-xl',
+ '2xl': 'text-2xl',
+ '3xl': 'text-3xl',
+ '4xl': 'text-4xl',
+ '5xl': 'text-5xl',
+ '6xl': 'text-6xl',
+ },
+ },
+});
+
+type IInputProps = React.ComponentProps &
+ VariantProps & { className?: string };
+const Input = React.forwardRef, IInputProps>(
+ function Input(
+ { className, variant = 'outline', size = 'md', ...props },
+ ref
+ ) {
+ return (
+
+ );
+ }
+);
+
+type IInputIconProps = React.ComponentProps &
+ VariantProps & {
+ className?: string;
+ height?: number;
+ width?: number;
+ };
+
+const InputIcon = React.forwardRef<
+ React.ComponentRef,
+ IInputIconProps
+>(function InputIcon({ className, size, ...props }, ref) {
+ const { size: parentSize } = useStyleContext(SCOPE);
+
+ if (typeof size === 'number') {
+ return (
+
+ );
+ } else if (
+ (props.height !== undefined || props.width !== undefined) &&
+ size === undefined
+ ) {
+ return (
+
+ );
+ }
+ return (
+
+ );
+});
+
+type IInputSlotProps = React.ComponentProps &
+ VariantProps & { className?: string };
+
+const InputSlot = React.forwardRef<
+ React.ComponentRef,
+ IInputSlotProps
+>(function InputSlot({ className, ...props }, ref) {
+ return (
+
+ );
+});
+
+type IInputFieldProps = React.ComponentProps &
+ VariantProps & { className?: string };
+
+const InputField = React.forwardRef<
+ React.ComponentRef,
+ IInputFieldProps
+>(function InputField({ className, ...props }, ref) {
+ const { variant: parentVariant, size: parentSize } = useStyleContext(SCOPE);
+
+ return (
+
+ );
+});
+
+Input.displayName = 'Input';
+InputIcon.displayName = 'InputIcon';
+InputSlot.displayName = 'InputSlot';
+InputField.displayName = 'InputField';
+
+export { Input, InputField, InputIcon, InputSlot };
diff --git a/lite.todo/components/ui/text/index.tsx b/lite.todo/components/ui/text/index.tsx
new file mode 100755
index 0000000..c3c7cf2
--- /dev/null
+++ b/lite.todo/components/ui/text/index.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+
+import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
+import { Text as RNText } from 'react-native';
+import { textStyle } from './styles';
+
+type ITextProps = React.ComponentProps &
+ VariantProps;
+
+const Text = React.forwardRef, ITextProps>(
+ function Text(
+ {
+ className,
+ isTruncated,
+ bold,
+ underline,
+ strikeThrough,
+ size = 'md',
+ sub,
+ italic,
+ highlight,
+ ...props
+ },
+ ref
+ ) {
+ return (
+
+ );
+ }
+);
+
+Text.displayName = 'Text';
+
+export { Text };
diff --git a/lite.todo/components/ui/text/index.web.tsx b/lite.todo/components/ui/text/index.web.tsx
new file mode 100755
index 0000000..0d008bc
--- /dev/null
+++ b/lite.todo/components/ui/text/index.web.tsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
+import { textStyle } from './styles';
+
+type ITextProps = React.ComponentProps<'span'> & VariantProps;
+
+const Text = React.forwardRef, ITextProps>(
+ function Text(
+ {
+ className,
+ isTruncated,
+ bold,
+ underline,
+ strikeThrough,
+ size = 'md',
+ sub,
+ italic,
+ highlight,
+ ...props
+ }: { className?: string } & ITextProps,
+ ref
+ ) {
+ return (
+
+ );
+ }
+);
+
+Text.displayName = 'Text';
+
+export { Text };
diff --git a/lite.todo/components/ui/text/styles.tsx b/lite.todo/components/ui/text/styles.tsx
new file mode 100755
index 0000000..4db83d1
--- /dev/null
+++ b/lite.todo/components/ui/text/styles.tsx
@@ -0,0 +1,47 @@
+import { tva } from '@gluestack-ui/utils/nativewind-utils';
+import { isWeb } from '@gluestack-ui/utils/nativewind-utils';
+
+const baseStyle = isWeb
+ ? 'font-sans tracking-sm my-0 bg-transparent border-0 box-border display-inline list-none margin-0 padding-0 position-relative text-start no-underline whitespace-pre-wrap word-wrap-break-word'
+ : '';
+
+export const textStyle = tva({
+ base: `text-typography-700 font-body ${baseStyle}`,
+
+ variants: {
+ isTruncated: {
+ true: 'web:truncate',
+ },
+ bold: {
+ true: 'font-bold',
+ },
+ underline: {
+ true: 'underline',
+ },
+ strikeThrough: {
+ true: 'line-through',
+ },
+ size: {
+ '2xs': 'text-2xs',
+ 'xs': 'text-xs',
+ 'sm': 'text-sm',
+ 'md': 'text-base',
+ 'lg': 'text-lg',
+ 'xl': 'text-xl',
+ '2xl': 'text-2xl',
+ '3xl': 'text-3xl',
+ '4xl': 'text-4xl',
+ '5xl': 'text-5xl',
+ '6xl': 'text-6xl',
+ },
+ sub: {
+ true: 'text-xs',
+ },
+ italic: {
+ true: 'italic',
+ },
+ highlight: {
+ true: 'bg-yellow-500',
+ },
+ },
+});
diff --git a/lite.todo/components/ui/vstack/index.tsx b/lite.todo/components/ui/vstack/index.tsx
new file mode 100755
index 0000000..c935fcf
--- /dev/null
+++ b/lite.todo/components/ui/vstack/index.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
+import { View } from 'react-native';
+
+import { vstackStyle } from './styles';
+
+type IVStackProps = React.ComponentProps &
+ VariantProps;
+
+const VStack = React.forwardRef, IVStackProps>(
+ function VStack({ className, space, reversed, ...props }, ref) {
+ return (
+
+ );
+ }
+);
+
+VStack.displayName = 'VStack';
+
+export { VStack };
diff --git a/lite.todo/components/ui/vstack/index.web.tsx b/lite.todo/components/ui/vstack/index.web.tsx
new file mode 100755
index 0000000..54c8a67
--- /dev/null
+++ b/lite.todo/components/ui/vstack/index.web.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils';
+
+import { vstackStyle } from './styles';
+
+type IVStackProps = React.ComponentProps<'div'> &
+ VariantProps;
+
+const VStack = React.forwardRef, IVStackProps>(
+ function VStack({ className, space, reversed, ...props }, ref) {
+ return (
+
+ );
+ }
+);
+
+VStack.displayName = 'VStack';
+
+export { VStack };
diff --git a/lite.todo/components/ui/vstack/styles.tsx b/lite.todo/components/ui/vstack/styles.tsx
new file mode 100755
index 0000000..458534a
--- /dev/null
+++ b/lite.todo/components/ui/vstack/styles.tsx
@@ -0,0 +1,25 @@
+import { isWeb } from '@gluestack-ui/utils/nativewind-utils';
+import { tva } from '@gluestack-ui/utils/nativewind-utils';
+
+const baseStyle = isWeb
+ ? 'flex flex-col relative z-0 box-border border-0 list-none min-w-0 min-h-0 bg-transparent items-stretch m-0 p-0 text-decoration-none'
+ : '';
+
+export const vstackStyle = tva({
+ base: `flex-col ${baseStyle}`,
+ variants: {
+ space: {
+ 'xs': 'gap-1',
+ 'sm': 'gap-2',
+ 'md': 'gap-3',
+ 'lg': 'gap-4',
+ 'xl': 'gap-5',
+ '2xl': 'gap-6',
+ '3xl': 'gap-7',
+ '4xl': 'gap-8',
+ },
+ reversed: {
+ true: 'flex-col-reverse',
+ },
+ },
+});
diff --git a/lite.todo/index.ts b/lite.todo/index_backup.ts
similarity index 100%
rename from lite.todo/index.ts
rename to lite.todo/index_backup.ts
diff --git a/lite.todo/package-lock.json b/lite.todo/package-lock.json
index c91bb75..2e90448 100755
--- a/lite.todo/package-lock.json
+++ b/lite.todo/package-lock.json
@@ -9,19 +9,26 @@
"version": "1.0.0",
"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",
"react": "19.0.0",
"react-aria": "^3.33.0",
"react-dom": "^19.1.1",
"react-native": "0.79.6",
"react-native-reanimated": "^4.1.0",
- "react-native-safe-area-context": "^4.11.0",
+ "react-native-safe-area-context": "5.4.0",
+ "react-native-screens": "~4.11.1",
"react-native-svg": "^15.2.0",
"react-native-worklets": "^0.5.0",
"react-stately": "^3.39.0",
@@ -1860,6 +1867,15 @@
"resolve-from": "^5.0.0"
}
},
+ "node_modules/@expo/metro-runtime": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-5.0.4.tgz",
+ "integrity": "sha512-r694MeO+7Vi8IwOsDIDzH/Q5RPMt1kUDYbiTJwnO15nIqiDwlE8HU55UlRhffKZy6s5FmxQsZ8HA+T8DqUW8cQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react-native": "*"
+ }
+ },
"node_modules/@expo/osascript": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.2.5.tgz",
@@ -1940,6 +1956,18 @@
"integrity": "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==",
"license": "MIT"
},
+ "node_modules/@expo/server": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/@expo/server/-/server-0.6.3.tgz",
+ "integrity": "sha512-Ea7NJn9Xk1fe4YeJ86rObHSv/bm3u/6WiQPXEqXJ2GrfYpVab2Swoh9/PnSM3KjR64JAgKjArDn1HiPjITCfHA==",
+ "license": "MIT",
+ "dependencies": {
+ "abort-controller": "^3.0.0",
+ "debug": "^4.3.4",
+ "source-map-support": "~0.5.21",
+ "undici": "^6.18.2 || ^7.0.0"
+ }
+ },
"node_modules/@expo/spawn-async": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@expo/spawn-async/-/spawn-async-1.7.2.tgz",
@@ -2593,6 +2621,39 @@
"node": ">=14"
}
},
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz",
+ "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@react-aria/breadcrumbs": {
"version": "3.5.28",
"resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.28.tgz",
@@ -3507,6 +3568,18 @@
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
+ "node_modules/@react-native-async-storage/async-storage": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz",
+ "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==",
+ "license": "MIT",
+ "dependencies": {
+ "merge-options": "^3.0.4"
+ },
+ "peerDependencies": {
+ "react-native": "^0.0.0-0 || >=0.65 <1.0"
+ }
+ },
"node_modules/@react-native/assets-registry": {
"version": "0.79.6",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.6.tgz",
@@ -3785,6 +3858,113 @@
"integrity": "sha512-nGXMNMclZgzLUxijQQ38Dm3IAEhgxuySAWQHnljFtfB0JdaMwpe0Ox9H7Tp2OgrEA+EMEv+Od9ElKlHwGKmmvQ==",
"license": "MIT"
},
+ "node_modules/@react-navigation/bottom-tabs": {
+ "version": "7.4.7",
+ "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.4.7.tgz",
+ "integrity": "sha512-SQ4KuYV9yr3SV/thefpLWhAD0CU2CrBMG1l0w/QKl3GYuGWdN5OQmdQdmaPZGtsjjVOb+N9Qo7Tf6210P4TlpA==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-navigation/elements": "^2.6.4",
+ "color": "^4.2.3"
+ },
+ "peerDependencies": {
+ "@react-navigation/native": "^7.1.17",
+ "react": ">= 18.2.0",
+ "react-native": "*",
+ "react-native-safe-area-context": ">= 4.0.0",
+ "react-native-screens": ">= 4.0.0"
+ }
+ },
+ "node_modules/@react-navigation/core": {
+ "version": "7.12.4",
+ "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.12.4.tgz",
+ "integrity": "sha512-xLFho76FA7v500XID5z/8YfGTvjQPw7/fXsq4BIrVSqetNe/o/v+KAocEw4ots6kyv3XvSTyiWKh2g3pN6xZ9Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-navigation/routers": "^7.5.1",
+ "escape-string-regexp": "^4.0.0",
+ "nanoid": "^3.3.11",
+ "query-string": "^7.1.3",
+ "react-is": "^19.1.0",
+ "use-latest-callback": "^0.2.4",
+ "use-sync-external-store": "^1.5.0"
+ },
+ "peerDependencies": {
+ "react": ">= 18.2.0"
+ }
+ },
+ "node_modules/@react-navigation/core/node_modules/react-is": {
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz",
+ "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==",
+ "license": "MIT"
+ },
+ "node_modules/@react-navigation/elements": {
+ "version": "2.6.4",
+ "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.6.4.tgz",
+ "integrity": "sha512-O3X9vWXOEhAO56zkQS7KaDzL8BvjlwZ0LGSteKpt1/k6w6HONG+2Wkblrb057iKmehTkEkQMzMLkXiuLmN5x9Q==",
+ "license": "MIT",
+ "dependencies": {
+ "color": "^4.2.3",
+ "use-latest-callback": "^0.2.4",
+ "use-sync-external-store": "^1.5.0"
+ },
+ "peerDependencies": {
+ "@react-native-masked-view/masked-view": ">= 0.2.0",
+ "@react-navigation/native": "^7.1.17",
+ "react": ">= 18.2.0",
+ "react-native": "*",
+ "react-native-safe-area-context": ">= 4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@react-native-masked-view/masked-view": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-navigation/native": {
+ "version": "7.1.17",
+ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.17.tgz",
+ "integrity": "sha512-uEcYWi1NV+2Qe1oELfp9b5hTYekqWATv2cuwcOAg5EvsIsUPtzFrKIasgUXLBRGb9P7yR5ifoJ+ug4u6jdqSTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-navigation/core": "^7.12.4",
+ "escape-string-regexp": "^4.0.0",
+ "fast-deep-equal": "^3.1.3",
+ "nanoid": "^3.3.11",
+ "use-latest-callback": "^0.2.4"
+ },
+ "peerDependencies": {
+ "react": ">= 18.2.0",
+ "react-native": "*"
+ }
+ },
+ "node_modules/@react-navigation/native-stack": {
+ "version": "7.3.26",
+ "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.3.26.tgz",
+ "integrity": "sha512-EjaBWzLZ76HJGOOcWCFf+h/M+Zg7M1RalYioDOb6ZdXHz7AwYNidruT3OUAQgSzg3gVLqvu5OYO0jFsNDPCZxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-navigation/elements": "^2.6.4",
+ "warn-once": "^0.1.1"
+ },
+ "peerDependencies": {
+ "@react-navigation/native": "^7.1.17",
+ "react": ">= 18.2.0",
+ "react-native": "*",
+ "react-native-safe-area-context": ">= 4.0.0",
+ "react-native-screens": ">= 4.0.0"
+ }
+ },
+ "node_modules/@react-navigation/routers": {
+ "version": "7.5.1",
+ "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.1.tgz",
+ "integrity": "sha512-pxipMW/iEBSUrjxz2cDD7fNwkqR4xoi0E/PcfTQGCcdJwLoaxzab5kSadBLj1MTJyT0YRrOXL9umHpXtp+Dv4w==",
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11"
+ }
+ },
"node_modules/@react-stately/calendar": {
"version": "3.8.4",
"resolved": "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.8.4.tgz",
@@ -5530,6 +5710,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/client-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
+ "license": "MIT"
+ },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@@ -5594,6 +5780,19 @@
"node": ">=6"
}
},
+ "node_modules/color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=12.5.0"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -5612,6 +5811,16 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
"node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
@@ -5903,6 +6112,15 @@
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
"license": "MIT"
},
+ "node_modules/decode-uri-component": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
+ "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@@ -6323,6 +6541,20 @@
"react": "*"
}
},
+ "node_modules/expo-linking": {
+ "version": "7.1.7",
+ "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-7.1.7.tgz",
+ "integrity": "sha512-ZJaH1RIch2G/M3hx2QJdlrKbYFUTOjVVW4g39hfxrE5bPX9xhZUYXqxqQtzMNl1ylAevw9JkgEfWbBWddbZ3UA==",
+ "license": "MIT",
+ "dependencies": {
+ "expo-constants": "~17.1.7",
+ "invariant": "^2.2.4"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/expo-modules-autolinking": {
"version": "2.1.14",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.1.14.tgz",
@@ -6350,6 +6582,60 @@
"invariant": "^2.2.4"
}
},
+ "node_modules/expo-router": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-5.1.5.tgz",
+ "integrity": "sha512-VPhS21DPP+riJIUshs/qpb11L/nzmRK7N7mqSFCr/mjpziznYu/qS+BPeQ88akIuXv6QsXipY5UTfYINdV+P0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@expo/metro-runtime": "5.0.4",
+ "@expo/schema-utils": "^0.1.0",
+ "@expo/server": "^0.6.3",
+ "@radix-ui/react-slot": "1.2.0",
+ "@react-navigation/bottom-tabs": "^7.3.10",
+ "@react-navigation/native": "^7.1.6",
+ "@react-navigation/native-stack": "^7.3.10",
+ "client-only": "^0.0.1",
+ "invariant": "^2.2.4",
+ "react-fast-compare": "^3.2.2",
+ "react-native-is-edge-to-edge": "^1.1.6",
+ "semver": "~7.6.3",
+ "server-only": "^0.0.1",
+ "shallowequal": "^1.1.0"
+ },
+ "peerDependencies": {
+ "@react-navigation/drawer": "^7.3.9",
+ "expo": "*",
+ "expo-constants": "*",
+ "expo-linking": "*",
+ "react-native-reanimated": "*",
+ "react-native-safe-area-context": "*",
+ "react-native-screens": "*"
+ },
+ "peerDependenciesMeta": {
+ "@react-navigation/drawer": {
+ "optional": true
+ },
+ "@testing-library/jest-native": {
+ "optional": true
+ },
+ "react-native-reanimated": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/expo-router/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/expo-status-bar": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-2.2.3.tgz",
@@ -6370,6 +6656,12 @@
"integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==",
"license": "Apache-2.0"
},
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -6434,6 +6726,15 @@
"node": ">=8"
}
},
+ "node_modules/filter-obj": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
+ "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@@ -6957,6 +7258,15 @@
"node": ">=0.12.0"
}
},
+ "node_modules/is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@@ -7695,6 +8005,17 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lucide-react-native": {
+ "version": "0.510.0",
+ "resolved": "https://registry.npmjs.org/lucide-react-native/-/lucide-react-native-0.510.0.tgz",
+ "integrity": "sha512-Ggy6NyWNcb/Wp9I0Kguwab5FRjr3GYsxNi0Nmy+skur1yj9TKiF2vcXPL7zWkkcgqk8WWKiIvmN3CntWvaDR1w==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0",
+ "react-native": "*",
+ "react-native-svg": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0"
+ }
+ },
"node_modules/makeerror": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
@@ -7722,6 +8043,18 @@
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
"license": "MIT"
},
+ "node_modules/merge-options": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
+ "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-plain-obj": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -9039,6 +9372,24 @@
"qrcode-terminal": "bin/qrcode-terminal.js"
}
},
+ "node_modules/query-string": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
+ "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
+ "license": "MIT",
+ "dependencies": {
+ "decode-uri-component": "^0.2.2",
+ "filter-obj": "^1.1.0",
+ "split-on-first": "^1.0.0",
+ "strict-uri-encode": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
@@ -9199,6 +9550,24 @@
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
"license": "MIT"
},
+ "node_modules/react-fast-compare": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
+ "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
+ "license": "MIT"
+ },
+ "node_modules/react-freeze": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz",
+ "integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": ">=17.0.0"
+ }
+ },
"node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
@@ -9356,10 +9725,25 @@
}
},
"node_modules/react-native-safe-area-context": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.11.0.tgz",
- "integrity": "sha512-Bg7bozxEB+ZS+H3tVYs5yY1cvxNXgR6nRQwpSMkYR9IN5CbxohLnSprrOPG/ostTCd4F6iCk0c51pExEhifSKQ==",
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.4.0.tgz",
+ "integrity": "sha512-JaEThVyJcLhA+vU0NU8bZ0a1ih6GiF4faZ+ArZLqpYbL6j7R3caRqj+mE3lEtKCuHgwjLg3bCxLL1GPUJZVqUA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/react-native-screens": {
+ "version": "4.11.1",
+ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.11.1.tgz",
+ "integrity": "sha512-F0zOzRVa3ptZfLpD0J8ROdo+y1fEPw+VBFq1MTY/iyDu08al7qFUO5hLMd+EYMda5VXGaTFCa8q7bOppUszhJw==",
"license": "MIT",
+ "dependencies": {
+ "react-freeze": "^1.0.0",
+ "react-native-is-edge-to-edge": "^1.1.7",
+ "warn-once": "^0.1.0"
+ },
"peerDependencies": {
"react": "*",
"react-native": "*"
@@ -10089,12 +10473,24 @@
"node": ">= 0.8"
}
},
+ "node_modules/server-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz",
+ "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==",
+ "license": "MIT"
+ },
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -10163,6 +10559,21 @@
"node": ">= 5.10.0"
}
},
+ "node_modules/simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "node_modules/simple-swizzle/node_modules/is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
+ "license": "MIT"
+ },
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -10224,6 +10635,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/split-on-first": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
+ "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -10287,6 +10707,15 @@
"node": ">= 0.10.0"
}
},
+ "node_modules/strict-uri-encode": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
+ "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@@ -10874,6 +11303,15 @@
"browserslist": ">= 4.21.0"
}
},
+ "node_modules/use-latest-callback": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.4.tgz",
+ "integrity": "sha512-LS2s2n1usUUnDq4oVh1ca6JFX9uSqUncTfAm44WMg0v6TxL7POUTk1B044NH8TeLkFbNajIsgDHcgNpNzZucdg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
"node_modules/use-sync-external-store": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
diff --git a/lite.todo/package.json b/lite.todo/package.json
index 7386c9f..c1afc6f 100755
--- a/lite.todo/package.json
+++ b/lite.todo/package.json
@@ -1,33 +1,40 @@
{
"name": "lite.todo",
"version": "1.0.0",
- "main": "index.ts",
+ "main": "expo-router/entry",
"scripts": {
- "start": "expo start",
- "android": "expo start --android",
- "ios": "expo start --ios",
- "web": "expo start --web"
+ "start": "expo start --offline",
+ "android": "expo start --android --offline",
+ "ios": "expo start --ios --offline",
+ "web": "expo start --web --offline"
},
"dependencies": {
"@expo/html-elements": "^0.10.1",
"@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-status-bar": "~2.2.3",
+ "lucide-react-native": "^0.510.0",
"nativewind": "^4.1.23",
"react": "19.0.0",
"react-aria": "^3.33.0",
"react-dom": "^19.1.1",
"react-native": "0.79.6",
"react-native-reanimated": "^4.1.0",
- "react-native-safe-area-context": "^4.11.0",
+ "react-native-safe-area-context": "5.4.0",
"react-native-svg": "^15.2.0",
"react-native-worklets": "^0.5.0",
"react-stately": "^3.39.0",
"tailwind-variants": "^0.1.20",
- "tailwindcss": "^3.4.17"
+ "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"
},
"devDependencies": {
"@babel/core": "^7.25.2",
diff --git a/lite.todo/store/useTodos.ts b/lite.todo/store/useTodos.ts
new file mode 100755
index 0000000..9c96c1a
--- /dev/null
+++ b/lite.todo/store/useTodos.ts
@@ -0,0 +1,90 @@
+import { useEffect, useMemo, useRef, useState } from "react";
+import AsyncStorage from "@react-native-async-storage/async-storage";
+
+export type Todo = {
+ id: string;
+ title: string;
+ done: boolean;
+ createdAt: number;
+};
+
+const STORAGE_KEY = "todos.v1";
+const DEBOUNCE_MS = 300;
+
+export function useTodos() {
+ const [todos, setTodos] = useState([]);
+ const timer = useRef | null>(null);
+
+ // load once
+ useEffect(() => {
+ (async () => {
+ const raw = await AsyncStorage.getItem(STORAGE_KEY);
+ if (raw) setTodos(JSON.parse(raw));
+ })();
+ }, []);
+
+ // debounce save
+ useEffect(() => {
+ if (timer.current) clearTimeout(timer.current);
+ timer.current = setTimeout(() => {
+ AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(todos)).catch((e) => {
+ console.warn("Debounce warn: ", e);
+ });
+ }, DEBOUNCE_MS);
+
+ return () => {
+ if (timer.current) clearTimeout(timer.current);
+ };
+ }, [todos]);
+
+ const activeTodos = useMemo(() => todos.filter((t) => !t.done), [todos]);
+ const doneTodos = useMemo(() => todos.filter((t) => t.done), [todos]);
+ const leftCount = useMemo(() => activeTodos.length, [activeTodos]);
+
+ const add = (title: string) => {
+ const t = title.trim();
+ if (!t) return;
+ const id = `${Date.now}-${Math.random().toString(36).slice(2, 8)}`;
+ setTodos([
+ {
+ id,
+ title: t,
+ done: false,
+ createdAt: Date.now(),
+ },
+ ...todos,
+ ]);
+ };
+
+ const toggle = (id: string) =>
+ setTodos(
+ todos.map((it) =>
+ it.id === id
+ ? {
+ ...it,
+ done: !it.done,
+ }
+ : it
+ )
+ );
+
+ const remove = (id: string) => setTodos(todos.filter((it) => it.id !== id));
+
+ const clearDone = () => setTodos(todos.filter((it) => !it.done));
+
+ const completeAll = () =>
+ setTodos(todos.map((it) => ({ ...it, done: true })));
+
+ return {
+ todos,
+ setTodos,
+ activeTodos,
+ doneTodos,
+ leftCount,
+ add,
+ toggle,
+ remove,
+ clearDone,
+ completeAll,
+ };
+}