parent
5c60fb968e
commit
187b2d52f7
@ -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 ( |
|
||||||
|
|
||||||
<GluestackUIProvider mode="light"> |
|
||||||
<View style={styles.container}> |
|
||||||
<Text>Open up App.tsx to start working on your app!</Text> |
|
||||||
<StatusBar style="auto" /> |
|
||||||
</View> |
|
||||||
</GluestackUIProvider> |
|
||||||
|
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
const styles = StyleSheet.create({ |
|
||||||
container: { |
|
||||||
flex: 1, |
|
||||||
backgroundColor: '#fff', |
|
||||||
alignItems: 'center', |
|
||||||
justifyContent: 'center', |
|
||||||
}, |
|
||||||
}); |
|
@ -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 ( |
||||||
|
<GluestackUIProvider mode="light"> |
||||||
|
<View style={styles.container}> |
||||||
|
<Text>Open up App.tsx to start working on your app!</Text> |
||||||
|
<StatusBar style="auto" /> |
||||||
|
</View> |
||||||
|
</GluestackUIProvider> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
const styles = StyleSheet.create({ |
||||||
|
container: { |
||||||
|
flex: 1, |
||||||
|
backgroundColor: "#fff", |
||||||
|
alignItems: "center", |
||||||
|
justifyContent: "center", |
||||||
|
}, |
||||||
|
}); |
@ -0,0 +1,41 @@ |
|||||||
|
import { Tabs } from "expo-router"; |
||||||
|
import { MaterialIcons } from "@expo/vector-icons"; |
||||||
|
|
||||||
|
export default function TabLayout() { |
||||||
|
return ( |
||||||
|
<Tabs |
||||||
|
screenOptions={{ |
||||||
|
headerShown: false, |
||||||
|
tabBarStyle: { height: 56 }, |
||||||
|
tabBarLabelStyle: { fontSize: 12 }, |
||||||
|
}} |
||||||
|
> |
||||||
|
<Tabs.Screen |
||||||
|
name="index" |
||||||
|
options={{ |
||||||
|
title: "할 일", |
||||||
|
tabBarIcon: ({ color, focused, size }) => ( |
||||||
|
<MaterialIcons |
||||||
|
name={focused ? "check-box" : "check-box-outline-blank"} |
||||||
|
size={size ?? 24} |
||||||
|
color={color} |
||||||
|
/> |
||||||
|
), |
||||||
|
}} |
||||||
|
/> |
||||||
|
<Tabs.Screen |
||||||
|
name="done" |
||||||
|
options={{ |
||||||
|
title: "완료됨", |
||||||
|
tabBarIcon: ({ color, focused, size }) => ( |
||||||
|
<MaterialIcons |
||||||
|
name={focused ? "done-all" : "done"} |
||||||
|
size={size ?? 24} |
||||||
|
color={color} |
||||||
|
/> |
||||||
|
), |
||||||
|
}} |
||||||
|
/> |
||||||
|
</Tabs> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,5 @@ |
|||||||
|
import { SafeAreaView } from "react-native-safe-area-context"; |
||||||
|
|
||||||
|
export default function DoneTab() { |
||||||
|
return <SafeAreaView className="flex-1"></SafeAreaView>; |
||||||
|
} |
@ -0,0 +1,5 @@ |
|||||||
|
import { SafeAreaView } from "react-native-safe-area-context"; |
||||||
|
|
||||||
|
export default function ActiveTab() { |
||||||
|
return <SafeAreaView className="flex-1"></SafeAreaView>; |
||||||
|
} |
@ -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 ( |
||||||
|
<GluestackUIProvider mode="light"> |
||||||
|
<SafeAreaView style={{ flex: 1 }}> |
||||||
|
<StatusBar style="auto" /> |
||||||
|
<Stack screenOptions={{ headerShown: false }} /> |
||||||
|
</SafeAreaView> |
||||||
|
</GluestackUIProvider> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
const styles = StyleSheet.create({ |
||||||
|
container: { |
||||||
|
flex: 1, |
||||||
|
backgroundColor: "#fff", |
||||||
|
// alignItems: "center",
|
||||||
|
// justifyContent: "center",
|
||||||
|
}, |
||||||
|
}); |
@ -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 ( |
||||||
|
<FlatList |
||||||
|
data={data} |
||||||
|
keyExtractor={(it) => it.id} |
||||||
|
contentContainerStyle={{ paddingBottom: 80 }} |
||||||
|
renderItem={({ item }) => ( |
||||||
|
<HStack className="items-center py-2"> |
||||||
|
<Checkbox |
||||||
|
isChecked={item.done} |
||||||
|
onChange={(_isChecked) => onToggle(item.id)} |
||||||
|
value={item.title} |
||||||
|
> |
||||||
|
<CheckboxIndicator> |
||||||
|
<CheckboxIcon /> |
||||||
|
</CheckboxIndicator> |
||||||
|
<CheckboxLabel className="data-[checked=true]:line-through"> |
||||||
|
{item.title} |
||||||
|
</CheckboxLabel> |
||||||
|
</Checkbox> |
||||||
|
{onRemove ? ( |
||||||
|
<Button |
||||||
|
variant={GS.variant.link} |
||||||
|
action={GS.action.negative} |
||||||
|
onPress={() => onRemove(item.id)} |
||||||
|
> |
||||||
|
<ButtonText>삭제</ButtonText> |
||||||
|
</Button> |
||||||
|
) : null} |
||||||
|
</HStack> |
||||||
|
)} |
||||||
|
ListEmptyComponent={ |
||||||
|
<Text className="text-neutral-400 mt-6 text-center"> |
||||||
|
할 일이 없습니다. |
||||||
|
</Text> |
||||||
|
} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
@ -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<typeof UIButton>, |
||||||
|
'context' |
||||||
|
> & |
||||||
|
VariantProps<typeof buttonStyle> & { className?: string }; |
||||||
|
|
||||||
|
const Button = React.forwardRef< |
||||||
|
React.ElementRef<typeof UIButton>, |
||||||
|
IButtonProps |
||||||
|
>( |
||||||
|
( |
||||||
|
{ className, variant = 'solid', size = 'md', action = 'primary', ...props }, |
||||||
|
ref |
||||||
|
) => { |
||||||
|
return ( |
||||||
|
<UIButton |
||||||
|
ref={ref} |
||||||
|
{...props} |
||||||
|
className={buttonStyle({ variant, size, action, class: className })} |
||||||
|
context={{ variant, size, action }} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
type IButtonTextProps = React.ComponentPropsWithoutRef<typeof UIButton.Text> & |
||||||
|
VariantProps<typeof buttonTextStyle> & { className?: string }; |
||||||
|
|
||||||
|
const ButtonText = React.forwardRef< |
||||||
|
React.ElementRef<typeof UIButton.Text>, |
||||||
|
IButtonTextProps |
||||||
|
>(({ className, variant, size, action, ...props }, ref) => { |
||||||
|
const { |
||||||
|
variant: parentVariant, |
||||||
|
size: parentSize, |
||||||
|
action: parentAction, |
||||||
|
} = useStyleContext(SCOPE); |
||||||
|
|
||||||
|
return ( |
||||||
|
<UIButton.Text |
||||||
|
ref={ref} |
||||||
|
{...props} |
||||||
|
className={buttonTextStyle({ |
||||||
|
parentVariants: { |
||||||
|
variant: parentVariant, |
||||||
|
size: parentSize, |
||||||
|
action: parentAction, |
||||||
|
}, |
||||||
|
variant: variant as 'link' | 'outline' | 'solid' | undefined, |
||||||
|
size, |
||||||
|
action: action as |
||||||
|
| 'primary' |
||||||
|
| 'secondary' |
||||||
|
| 'positive' |
||||||
|
| 'negative' |
||||||
|
| undefined, |
||||||
|
class: className, |
||||||
|
})} |
||||||
|
/> |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
const ButtonSpinner = UIButton.Spinner; |
||||||
|
|
||||||
|
type IButtonIcon = React.ComponentPropsWithoutRef<typeof UIButton.Icon> & |
||||||
|
VariantProps<typeof buttonIconStyle> & { |
||||||
|
className?: string | undefined; |
||||||
|
as?: React.ElementType; |
||||||
|
height?: number; |
||||||
|
width?: number; |
||||||
|
}; |
||||||
|
|
||||||
|
const ButtonIcon = React.forwardRef< |
||||||
|
React.ElementRef<typeof UIButton.Icon>, |
||||||
|
IButtonIcon |
||||||
|
>(({ className, size, ...props }, ref) => { |
||||||
|
const { |
||||||
|
variant: parentVariant, |
||||||
|
size: parentSize, |
||||||
|
action: parentAction, |
||||||
|
} = useStyleContext(SCOPE); |
||||||
|
|
||||||
|
if (typeof size === 'number') { |
||||||
|
return ( |
||||||
|
<UIButton.Icon |
||||||
|
ref={ref} |
||||||
|
{...props} |
||||||
|
className={buttonIconStyle({ class: className })} |
||||||
|
size={size} |
||||||
|
/> |
||||||
|
); |
||||||
|
} else if ( |
||||||
|
(props.height !== undefined || props.width !== undefined) && |
||||||
|
size === undefined |
||||||
|
) { |
||||||
|
return ( |
||||||
|
<UIButton.Icon |
||||||
|
ref={ref} |
||||||
|
{...props} |
||||||
|
className={buttonIconStyle({ class: className })} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
return ( |
||||||
|
<UIButton.Icon |
||||||
|
{...props} |
||||||
|
className={buttonIconStyle({ |
||||||
|
parentVariants: { |
||||||
|
size: parentSize, |
||||||
|
variant: parentVariant, |
||||||
|
action: parentAction, |
||||||
|
}, |
||||||
|
size, |
||||||
|
class: className, |
||||||
|
})} |
||||||
|
ref={ref} |
||||||
|
/> |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
type IButtonGroupProps = React.ComponentPropsWithoutRef<typeof UIButton.Group> & |
||||||
|
VariantProps<typeof buttonGroupStyle>; |
||||||
|
|
||||||
|
const ButtonGroup = React.forwardRef< |
||||||
|
React.ElementRef<typeof UIButton.Group>, |
||||||
|
IButtonGroupProps |
||||||
|
>( |
||||||
|
( |
||||||
|
{ |
||||||
|
className, |
||||||
|
space = 'md', |
||||||
|
isAttached = false, |
||||||
|
flexDirection = 'column', |
||||||
|
...props |
||||||
|
}, |
||||||
|
ref |
||||||
|
) => { |
||||||
|
return ( |
||||||
|
<UIButton.Group |
||||||
|
className={buttonGroupStyle({ |
||||||
|
class: className, |
||||||
|
space, |
||||||
|
isAttached: isAttached as boolean, |
||||||
|
flexDirection: flexDirection as any, |
||||||
|
})} |
||||||
|
{...props} |
||||||
|
ref={ref} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
Button.displayName = 'Button'; |
||||||
|
ButtonText.displayName = 'ButtonText'; |
||||||
|
ButtonSpinner.displayName = 'ButtonSpinner'; |
||||||
|
ButtonIcon.displayName = 'ButtonIcon'; |
||||||
|
ButtonGroup.displayName = 'ButtonGroup'; |
||||||
|
|
||||||
|
export { Button, ButtonText, ButtonSpinner, ButtonIcon, ButtonGroup }; |
@ -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<typeof View>, |
||||||
|
ViewProps |
||||||
|
>(function IndicatorWrapper({ ...props }, ref) { |
||||||
|
return <View {...props} ref={ref} />; |
||||||
|
}); |
||||||
|
|
||||||
|
const LabelWrapper = React.forwardRef< |
||||||
|
React.ComponentRef<typeof Text>, |
||||||
|
TextProps |
||||||
|
>(function LabelWrapper({ ...props }, ref) { |
||||||
|
return <Text {...props} ref={ref} />; |
||||||
|
}); |
||||||
|
|
||||||
|
const IconWrapper = React.forwardRef< |
||||||
|
React.ComponentRef<typeof PrimitiveIcon>, |
||||||
|
IPrimitiveIcon |
||||||
|
>(function IconWrapper({ ...props }, ref) { |
||||||
|
return <UIIcon {...props} ref={ref} />; |
||||||
|
}); |
||||||
|
|
||||||
|
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<typeof UICheckbox> & |
||||||
|
VariantProps<typeof checkboxStyle>; |
||||||
|
|
||||||
|
const Checkbox = React.forwardRef< |
||||||
|
React.ComponentRef<typeof UICheckbox>, |
||||||
|
ICheckboxProps |
||||||
|
>(function Checkbox({ className, size = 'md', ...props }, ref) { |
||||||
|
return ( |
||||||
|
<UICheckbox |
||||||
|
className={checkboxStyle({ |
||||||
|
class: className, |
||||||
|
size, |
||||||
|
})} |
||||||
|
{...props} |
||||||
|
context={{ |
||||||
|
size, |
||||||
|
}} |
||||||
|
ref={ref} |
||||||
|
/> |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
type ICheckboxIndicatorProps = React.ComponentPropsWithoutRef< |
||||||
|
typeof UICheckbox.Indicator |
||||||
|
> & |
||||||
|
VariantProps<typeof checkboxIndicatorStyle>; |
||||||
|
|
||||||
|
const CheckboxIndicator = React.forwardRef< |
||||||
|
React.ComponentRef<typeof UICheckbox.Indicator>, |
||||||
|
ICheckboxIndicatorProps |
||||||
|
>(function CheckboxIndicator({ className, ...props }, ref) { |
||||||
|
const { size: parentSize } = useStyleContext(SCOPE); |
||||||
|
|
||||||
|
return ( |
||||||
|
<UICheckbox.Indicator |
||||||
|
className={checkboxIndicatorStyle({ |
||||||
|
parentVariants: { |
||||||
|
size: parentSize, |
||||||
|
}, |
||||||
|
class: className, |
||||||
|
})} |
||||||
|
{...props} |
||||||
|
ref={ref} |
||||||
|
/> |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
type ICheckboxLabelProps = React.ComponentPropsWithoutRef< |
||||||
|
typeof UICheckbox.Label |
||||||
|
> & |
||||||
|
VariantProps<typeof checkboxLabelStyle>; |
||||||
|
const CheckboxLabel = React.forwardRef< |
||||||
|
React.ComponentRef<typeof UICheckbox.Label>, |
||||||
|
ICheckboxLabelProps |
||||||
|
>(function CheckboxLabel({ className, ...props }, ref) { |
||||||
|
const { size: parentSize } = useStyleContext(SCOPE); |
||||||
|
return ( |
||||||
|
<UICheckbox.Label |
||||||
|
className={checkboxLabelStyle({ |
||||||
|
parentVariants: { |
||||||
|
size: parentSize, |
||||||
|
}, |
||||||
|
class: className, |
||||||
|
})} |
||||||
|
{...props} |
||||||
|
ref={ref} |
||||||
|
/> |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
type ICheckboxIconProps = React.ComponentPropsWithoutRef< |
||||||
|
typeof UICheckbox.Icon |
||||||
|
> & |
||||||
|
VariantProps<typeof checkboxIconStyle>; |
||||||
|
|
||||||
|
const CheckboxIcon = React.forwardRef< |
||||||
|
React.ComponentRef<typeof UICheckbox.Icon>, |
||||||
|
ICheckboxIconProps |
||||||
|
>(function CheckboxIcon({ className, size, ...props }, ref) { |
||||||
|
const { size: parentSize } = useStyleContext(SCOPE); |
||||||
|
|
||||||
|
if (typeof size === 'number') { |
||||||
|
return ( |
||||||
|
<UICheckbox.Icon |
||||||
|
ref={ref} |
||||||
|
{...props} |
||||||
|
className={checkboxIconStyle({ class: className })} |
||||||
|
size={size} |
||||||
|
/> |
||||||
|
); |
||||||
|
} else if ( |
||||||
|
(props.height !== undefined || props.width !== undefined) && |
||||||
|
size === undefined |
||||||
|
) { |
||||||
|
return ( |
||||||
|
<UICheckbox.Icon |
||||||
|
ref={ref} |
||||||
|
{...props} |
||||||
|
className={checkboxIconStyle({ class: className })} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<UICheckbox.Icon |
||||||
|
className={checkboxIconStyle({ |
||||||
|
parentVariants: { |
||||||
|
size: parentSize, |
||||||
|
}, |
||||||
|
size, |
||||||
|
class: className, |
||||||
|
})} |
||||||
|
{...props} |
||||||
|
ref={ref} |
||||||
|
/> |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
Checkbox.displayName = 'Checkbox'; |
||||||
|
CheckboxIndicator.displayName = 'CheckboxIndicator'; |
||||||
|
CheckboxLabel.displayName = 'CheckboxLabel'; |
||||||
|
CheckboxIcon.displayName = 'CheckboxIcon'; |
||||||
|
|
||||||
|
export { |
||||||
|
Checkbox, |
||||||
|
CheckboxIndicator, |
||||||
|
CheckboxLabel, |
||||||
|
CheckboxIcon, |
||||||
|
CheckboxGroup, |
||||||
|
}; |
@ -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; |
@ -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<typeof hstackStyle>; |
||||||
|
|
||||||
|
const HStack = React.forwardRef<React.ComponentRef<typeof View>, IHStackProps>( |
||||||
|
function HStack({ className, space, reversed, ...props }, ref) { |
||||||
|
return ( |
||||||
|
<View |
||||||
|
className={hstackStyle({ |
||||||
|
space, |
||||||
|
reversed: reversed as boolean, |
||||||
|
class: className, |
||||||
|
})} |
||||||
|
{...props} |
||||||
|
ref={ref} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
HStack.displayName = 'HStack'; |
||||||
|
|
||||||
|
export { HStack }; |
@ -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<typeof hstackStyle>; |
||||||
|
|
||||||
|
const HStack = React.forwardRef<React.ComponentRef<'div'>, IHStackProps>( |
||||||
|
function HStack({ className, space, reversed, ...props }, ref) { |
||||||
|
return ( |
||||||
|
<div |
||||||
|
className={hstackStyle({ |
||||||
|
space, |
||||||
|
reversed: reversed as boolean, |
||||||
|
class: className, |
||||||
|
})} |
||||||
|
{...props} |
||||||
|
ref={ref} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
HStack.displayName = 'HStack'; |
||||||
|
|
||||||
|
export { HStack }; |
@ -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', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}); |
@ -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<typeof UIInput> & |
||||||
|
VariantProps<typeof inputStyle> & { className?: string }; |
||||||
|
const Input = React.forwardRef<React.ComponentRef<typeof UIInput>, IInputProps>( |
||||||
|
function Input( |
||||||
|
{ className, variant = 'outline', size = 'md', ...props }, |
||||||
|
ref |
||||||
|
) { |
||||||
|
return ( |
||||||
|
<UIInput |
||||||
|
ref={ref} |
||||||
|
{...props} |
||||||
|
className={inputStyle({ variant, size, class: className })} |
||||||
|
context={{ variant, size }} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
type IInputIconProps = React.ComponentProps<typeof UIInput.Icon> & |
||||||
|
VariantProps<typeof inputIconStyle> & { |
||||||
|
className?: string; |
||||||
|
height?: number; |
||||||
|
width?: number; |
||||||
|
}; |
||||||
|
|
||||||
|
const InputIcon = React.forwardRef< |
||||||
|
React.ComponentRef<typeof UIInput.Icon>, |
||||||
|
IInputIconProps |
||||||
|
>(function InputIcon({ className, size, ...props }, ref) { |
||||||
|
const { size: parentSize } = useStyleContext(SCOPE); |
||||||
|
|
||||||
|
if (typeof size === 'number') { |
||||||
|
return ( |
||||||
|
<UIInput.Icon |
||||||
|
ref={ref} |
||||||
|
{...props} |
||||||
|
className={inputIconStyle({ class: className })} |
||||||
|
size={size} |
||||||
|
/> |
||||||
|
); |
||||||
|
} else if ( |
||||||
|
(props.height !== undefined || props.width !== undefined) && |
||||||
|
size === undefined |
||||||
|
) { |
||||||
|
return ( |
||||||
|
<UIInput.Icon |
||||||
|
ref={ref} |
||||||
|
{...props} |
||||||
|
className={inputIconStyle({ class: className })} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
return ( |
||||||
|
<UIInput.Icon |
||||||
|
ref={ref} |
||||||
|
{...props} |
||||||
|
className={inputIconStyle({ |
||||||
|
parentVariants: { |
||||||
|
size: parentSize, |
||||||
|
}, |
||||||
|
class: className, |
||||||
|
})} |
||||||
|
/> |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
type IInputSlotProps = React.ComponentProps<typeof UIInput.Slot> & |
||||||
|
VariantProps<typeof inputSlotStyle> & { className?: string }; |
||||||
|
|
||||||
|
const InputSlot = React.forwardRef< |
||||||
|
React.ComponentRef<typeof UIInput.Slot>, |
||||||
|
IInputSlotProps |
||||||
|
>(function InputSlot({ className, ...props }, ref) { |
||||||
|
return ( |
||||||
|
<UIInput.Slot |
||||||
|
ref={ref} |
||||||
|
{...props} |
||||||
|
className={inputSlotStyle({ |
||||||
|
class: className, |
||||||
|
})} |
||||||
|
/> |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
type IInputFieldProps = React.ComponentProps<typeof UIInput.Input> & |
||||||
|
VariantProps<typeof inputFieldStyle> & { className?: string }; |
||||||
|
|
||||||
|
const InputField = React.forwardRef< |
||||||
|
React.ComponentRef<typeof UIInput.Input>, |
||||||
|
IInputFieldProps |
||||||
|
>(function InputField({ className, ...props }, ref) { |
||||||
|
const { variant: parentVariant, size: parentSize } = useStyleContext(SCOPE); |
||||||
|
|
||||||
|
return ( |
||||||
|
<UIInput.Input |
||||||
|
ref={ref} |
||||||
|
{...props} |
||||||
|
className={inputFieldStyle({ |
||||||
|
parentVariants: { |
||||||
|
variant: parentVariant, |
||||||
|
size: parentSize, |
||||||
|
}, |
||||||
|
class: className, |
||||||
|
})} |
||||||
|
/> |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
Input.displayName = 'Input'; |
||||||
|
InputIcon.displayName = 'InputIcon'; |
||||||
|
InputSlot.displayName = 'InputSlot'; |
||||||
|
InputField.displayName = 'InputField'; |
||||||
|
|
||||||
|
export { Input, InputField, InputIcon, InputSlot }; |
@ -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<typeof RNText> & |
||||||
|
VariantProps<typeof textStyle>; |
||||||
|
|
||||||
|
const Text = React.forwardRef<React.ComponentRef<typeof RNText>, ITextProps>( |
||||||
|
function Text( |
||||||
|
{ |
||||||
|
className, |
||||||
|
isTruncated, |
||||||
|
bold, |
||||||
|
underline, |
||||||
|
strikeThrough, |
||||||
|
size = 'md', |
||||||
|
sub, |
||||||
|
italic, |
||||||
|
highlight, |
||||||
|
...props |
||||||
|
}, |
||||||
|
ref |
||||||
|
) { |
||||||
|
return ( |
||||||
|
<RNText |
||||||
|
className={textStyle({ |
||||||
|
isTruncated: isTruncated as boolean, |
||||||
|
bold: bold as boolean, |
||||||
|
underline: underline as boolean, |
||||||
|
strikeThrough: strikeThrough as boolean, |
||||||
|
size, |
||||||
|
sub: sub as boolean, |
||||||
|
italic: italic as boolean, |
||||||
|
highlight: highlight as boolean, |
||||||
|
class: className, |
||||||
|
})} |
||||||
|
{...props} |
||||||
|
ref={ref} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
Text.displayName = 'Text'; |
||||||
|
|
||||||
|
export { Text }; |
@ -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<typeof textStyle>; |
||||||
|
|
||||||
|
const Text = React.forwardRef<React.ComponentRef<'span'>, ITextProps>( |
||||||
|
function Text( |
||||||
|
{ |
||||||
|
className, |
||||||
|
isTruncated, |
||||||
|
bold, |
||||||
|
underline, |
||||||
|
strikeThrough, |
||||||
|
size = 'md', |
||||||
|
sub, |
||||||
|
italic, |
||||||
|
highlight, |
||||||
|
...props |
||||||
|
}: { className?: string } & ITextProps, |
||||||
|
ref |
||||||
|
) { |
||||||
|
return ( |
||||||
|
<span |
||||||
|
className={textStyle({ |
||||||
|
isTruncated: isTruncated as boolean, |
||||||
|
bold: bold as boolean, |
||||||
|
underline: underline as boolean, |
||||||
|
strikeThrough: strikeThrough as boolean, |
||||||
|
size, |
||||||
|
sub: sub as boolean, |
||||||
|
italic: italic as boolean, |
||||||
|
highlight: highlight as boolean, |
||||||
|
class: className, |
||||||
|
})} |
||||||
|
{...props} |
||||||
|
ref={ref} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
Text.displayName = 'Text'; |
||||||
|
|
||||||
|
export { Text }; |
@ -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', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}); |
@ -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<typeof View> & |
||||||
|
VariantProps<typeof vstackStyle>; |
||||||
|
|
||||||
|
const VStack = React.forwardRef<React.ComponentRef<typeof View>, IVStackProps>( |
||||||
|
function VStack({ className, space, reversed, ...props }, ref) { |
||||||
|
return ( |
||||||
|
<View |
||||||
|
className={vstackStyle({ |
||||||
|
space, |
||||||
|
reversed: reversed as boolean, |
||||||
|
class: className, |
||||||
|
})} |
||||||
|
{...props} |
||||||
|
ref={ref} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
VStack.displayName = 'VStack'; |
||||||
|
|
||||||
|
export { VStack }; |
@ -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<typeof vstackStyle>; |
||||||
|
|
||||||
|
const VStack = React.forwardRef<React.ComponentRef<'div'>, IVStackProps>( |
||||||
|
function VStack({ className, space, reversed, ...props }, ref) { |
||||||
|
return ( |
||||||
|
<div |
||||||
|
className={vstackStyle({ |
||||||
|
space, |
||||||
|
reversed: reversed as boolean, |
||||||
|
class: className, |
||||||
|
})} |
||||||
|
{...props} |
||||||
|
ref={ref} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
VStack.displayName = 'VStack'; |
||||||
|
|
||||||
|
export { VStack }; |
@ -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', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}); |
@ -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<Todo[]>([]); |
||||||
|
const timer = useRef<ReturnType<typeof setTimeout> | 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, |
||||||
|
}; |
||||||
|
} |
Loading…
Reference in new issue