From 187b2d52f76cad1e3b0300c40f7a00f3f27b2cf9 Mon Sep 17 00:00:00 2001 From: Peace Date: Thu, 4 Sep 2025 17:27:09 +0900 Subject: [PATCH] mfe --- lite.todo/App.tsx | 27 -- lite.todo/App_backup.tsx | 25 + lite.todo/app.json | 4 +- lite.todo/app/(tabs)/_layout.tsx | 41 ++ lite.todo/app/(tabs)/done.tsx | 5 + lite.todo/app/(tabs)/index.tsx | 5 + lite.todo/app/_layout.tsx | 27 ++ lite.todo/components/TodoList.tsx | 59 +++ lite.todo/components/ui/button/index.tsx | 439 +++++++++++++++++ lite.todo/components/ui/checkbox/index.tsx | 242 ++++++++++ .../gluestack-ui-token.ts | 19 + lite.todo/components/ui/hstack/index.tsx | 27 ++ lite.todo/components/ui/hstack/index.web.tsx | 26 + lite.todo/components/ui/hstack/styles.tsx | 25 + lite.todo/components/ui/input/index.tsx | 217 +++++++++ lite.todo/components/ui/text/index.tsx | 48 ++ lite.todo/components/ui/text/index.web.tsx | 45 ++ lite.todo/components/ui/text/styles.tsx | 47 ++ lite.todo/components/ui/vstack/index.tsx | 28 ++ lite.todo/components/ui/vstack/index.web.tsx | 27 ++ lite.todo/components/ui/vstack/styles.tsx | 25 + lite.todo/{index.ts => index_backup.ts} | 0 lite.todo/package-lock.json | 446 +++++++++++++++++- lite.todo/package.json | 21 +- lite.todo/store/useTodos.ts | 90 ++++ 25 files changed, 1926 insertions(+), 39 deletions(-) delete mode 100755 lite.todo/App.tsx create mode 100755 lite.todo/App_backup.tsx create mode 100755 lite.todo/app/(tabs)/_layout.tsx create mode 100755 lite.todo/app/(tabs)/done.tsx create mode 100755 lite.todo/app/(tabs)/index.tsx create mode 100755 lite.todo/app/_layout.tsx create mode 100755 lite.todo/components/TodoList.tsx create mode 100755 lite.todo/components/ui/button/index.tsx create mode 100755 lite.todo/components/ui/checkbox/index.tsx create mode 100755 lite.todo/components/ui/gluestack-ui-provider/gluestack-ui-token.ts create mode 100755 lite.todo/components/ui/hstack/index.tsx create mode 100755 lite.todo/components/ui/hstack/index.web.tsx create mode 100755 lite.todo/components/ui/hstack/styles.tsx create mode 100755 lite.todo/components/ui/input/index.tsx create mode 100755 lite.todo/components/ui/text/index.tsx create mode 100755 lite.todo/components/ui/text/index.web.tsx create mode 100755 lite.todo/components/ui/text/styles.tsx create mode 100755 lite.todo/components/ui/vstack/index.tsx create mode 100755 lite.todo/components/ui/vstack/index.web.tsx create mode 100755 lite.todo/components/ui/vstack/styles.tsx rename lite.todo/{index.ts => index_backup.ts} (100%) create mode 100755 lite.todo/store/useTodos.ts 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, + }; +}