parent
84ee08acba
commit
d118df7e9a
@ -0,0 +1,114 @@ |
||||
import { VStack } from "@/components/ui/vstack"; |
||||
import { Text } from "@/components/ui/text"; |
||||
import { View } from "react-native"; |
||||
import { |
||||
Checkbox, |
||||
CheckboxIcon, |
||||
CheckboxIndicator, |
||||
CheckboxLabel, |
||||
} from "@/components/ui/checkbox"; |
||||
import { useActionState, useEffect, useRef, useState } from "react"; |
||||
import { AddIcon, CheckIcon } from "@/components/ui/icon"; |
||||
import TodoContainer, { Todo } from "@/components/containers/todo-container"; |
||||
import { FormControl } from "@/components/ui/form-control"; |
||||
import { Input, InputField, InputIcon } from "@/components/ui/input"; |
||||
import { Pressable } from "@/components/ui/pressable"; |
||||
import uuid from "react-native-uuid"; |
||||
import AsyncStorage from "@react-native-async-storage/async-storage"; |
||||
|
||||
const STORAGE_KEY = "todos:v1"; |
||||
|
||||
export default function TodoTab() { |
||||
const [item, setItem] = useState(""); |
||||
const [todos, setTodos] = useState<Todo[]>([]); |
||||
|
||||
// 초기 로드
|
||||
useEffect(() => { |
||||
(async () => { |
||||
try { |
||||
const raw = await AsyncStorage.getItem(STORAGE_KEY); |
||||
if (raw) { |
||||
const parsed = JSON.parse(raw) as Todo[]; |
||||
setTodos(parsed); |
||||
} |
||||
} catch (e) { |
||||
console.warn("Failed to load todos: ", e); |
||||
} |
||||
})(); |
||||
}, []); |
||||
|
||||
// 변경 시 저장 (디바운스 300ms)
|
||||
const saveTimer = useRef<ReturnType<typeof setTimeout> | null>(null); |
||||
useEffect(() => { |
||||
if (saveTimer.current) clearTimeout(saveTimer.current); |
||||
saveTimer.current = setTimeout(async () => { |
||||
try { |
||||
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); |
||||
} catch (e) { |
||||
console.warn("Failed to save todos: ", e); |
||||
} |
||||
}, 300); |
||||
|
||||
return () => { |
||||
if (saveTimer.current) clearTimeout(saveTimer.current); |
||||
}; |
||||
}, [todos]); |
||||
|
||||
const addTodo = (task: string) => { |
||||
const lastTodo = todos[todos?.length - 1]; |
||||
if (lastTodo?.task !== "" && task !== "") { |
||||
setTodos([ |
||||
...todos, |
||||
{ |
||||
id: uuid.v4(), |
||||
task: task, |
||||
completed: false, |
||||
}, |
||||
]); |
||||
setItem(""); |
||||
} |
||||
}; |
||||
|
||||
const toggleTodo = (id: string) => { |
||||
const updatedTodos = todos?.map((todo) => { |
||||
if (todo.id === id) { |
||||
todo.completed = !todo.completed; |
||||
} |
||||
return todo; |
||||
}); |
||||
setTodos(updatedTodos); |
||||
}; |
||||
|
||||
const deleteTodo = (id: string) => { |
||||
const updatedTodos = todos.filter((todo) => todo.id !== id); |
||||
setTodos(updatedTodos); |
||||
}; |
||||
|
||||
return ( |
||||
<VStack className="flex-1 bg-secondary-100 md:items-center md:justify-center"> |
||||
<VStack className="rounded-md bg-secondary-100 md:h-[500px] md:w-[700px]"> |
||||
<FormControl className="my-4"> |
||||
<Input variant="underlined" size="sm" className="mx-6 my-2"> |
||||
<InputField |
||||
placeholder="다음 할 일은 무엇인가요?" |
||||
value={item} |
||||
onChangeText={(value) => setItem(value)} |
||||
onSubmitEditing={() => addTodo(item)} |
||||
/> |
||||
<Pressable onPress={() => addTodo(item)}> |
||||
<InputIcon as={AddIcon} className="cursor-pointer h-3 w-3" /> |
||||
</Pressable> |
||||
</Input> |
||||
</FormControl> |
||||
{todos?.map((todo: Todo, index: number) => ( |
||||
<TodoContainer |
||||
key={index} |
||||
todo={todo} |
||||
toggleTodo={toggleTodo} |
||||
deleteTodo={deleteTodo} |
||||
/> |
||||
))} |
||||
</VStack> |
||||
</VStack> |
||||
); |
||||
} |
@ -0,0 +1,64 @@ |
||||
import { |
||||
Checkbox, |
||||
CheckboxIcon, |
||||
CheckboxIndicator, |
||||
CheckboxLabel, |
||||
} from "../ui/checkbox"; |
||||
import { HStack } from "../ui/hstack"; |
||||
import { CheckIcon, CloseIcon, Icon } from "../ui/icon"; |
||||
import { Pressable } from "../ui/pressable"; |
||||
|
||||
export interface Todo { |
||||
id: string; |
||||
task: string; |
||||
completed: boolean; |
||||
} |
||||
|
||||
const TodoContainer = ({ |
||||
todo, |
||||
toggleTodo, |
||||
deleteTodo, |
||||
...props |
||||
}: { |
||||
todo: Todo; |
||||
toggleTodo: (id: string) => void; |
||||
deleteTodo: (id: string) => void; |
||||
}) => { |
||||
return ( |
||||
<HStack |
||||
{...props} |
||||
className="rounded-md hover:bg-secondary-200 justify-between items-center" |
||||
> |
||||
<Checkbox |
||||
onChange={(_isChecked) => toggleTodo(todo.id)} |
||||
size="sm" |
||||
aria-label={todo.task} |
||||
value={todo.task} |
||||
isChecked={todo.completed} |
||||
className="pl-6 py-2 flex-1" |
||||
> |
||||
<CheckboxIndicator> |
||||
<CheckboxIcon as={CheckIcon} /> |
||||
</CheckboxIndicator> |
||||
<CheckboxLabel className="text-sm data-[checked=true]:line-through"> |
||||
{todo.task} |
||||
</CheckboxLabel> |
||||
</Checkbox> |
||||
<Pressable className="pr-6 py-2" onPress={() => deleteTodo(todo.id)}> |
||||
{(state: any) => { |
||||
return ( |
||||
<Icon |
||||
as={CloseIcon} |
||||
size="xs" |
||||
className={ |
||||
state?.hovered ? "stroke-red-400" : "stroke-primary-50" |
||||
} |
||||
/> |
||||
); |
||||
}} |
||||
</Pressable> |
||||
</HStack> |
||||
); |
||||
}; |
||||
|
||||
export default TodoContainer; |
@ -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,468 @@ |
||||
'use client'; |
||||
import { Text, View } from 'react-native'; |
||||
import React from 'react'; |
||||
import { createFormControl } from '@gluestack-ui/core/form-control/creator'; |
||||
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 = 'FORM_CONTROL'; |
||||
|
||||
const formControlStyle = tva({ |
||||
base: 'flex flex-col', |
||||
variants: { |
||||
size: { |
||||
sm: '', |
||||
md: '', |
||||
lg: '', |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
const formControlErrorIconStyle = tva({ |
||||
base: 'text-error-700 fill-none', |
||||
variants: { |
||||
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 formControlErrorStyle = tva({ |
||||
base: 'flex flex-row justify-start items-center mt-1 gap-1', |
||||
}); |
||||
|
||||
const formControlErrorTextStyle = tva({ |
||||
base: 'text-error-700', |
||||
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', |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
const formControlHelperStyle = tva({ |
||||
base: 'flex flex-row justify-start items-center mt-1', |
||||
}); |
||||
|
||||
const formControlHelperTextStyle = tva({ |
||||
base: 'text-typography-500', |
||||
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-xs', |
||||
'md': 'text-sm', |
||||
'lg': 'text-base', |
||||
'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', |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
const formControlLabelStyle = tva({ |
||||
base: 'flex flex-row justify-start items-center mb-1', |
||||
}); |
||||
|
||||
const formControlLabelTextStyle = tva({ |
||||
base: 'font-medium text-typography-900', |
||||
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', |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
const formControlLabelAstrickStyle = tva({ |
||||
base: 'font-medium text-typography-900', |
||||
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', |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
type IFormControlLabelAstrickProps = React.ComponentPropsWithoutRef< |
||||
typeof Text |
||||
> & |
||||
VariantProps<typeof formControlLabelAstrickStyle>; |
||||
|
||||
const FormControlLabelAstrick = React.forwardRef< |
||||
React.ComponentRef<typeof Text>, |
||||
IFormControlLabelAstrickProps |
||||
>(function FormControlLabelAstrick({ className, ...props }, ref) { |
||||
const { size: parentSize } = useStyleContext(SCOPE); |
||||
|
||||
return ( |
||||
<Text |
||||
ref={ref} |
||||
className={formControlLabelAstrickStyle({ |
||||
parentVariants: { size: parentSize }, |
||||
class: className, |
||||
})} |
||||
{...props} |
||||
/> |
||||
); |
||||
}); |
||||
|
||||
export const UIFormControl = createFormControl({ |
||||
Root: withStyleContext(View, SCOPE), |
||||
Error: View, |
||||
ErrorText: Text, |
||||
ErrorIcon: UIIcon, |
||||
Label: View, |
||||
LabelText: Text, |
||||
LabelAstrick: FormControlLabelAstrick, |
||||
Helper: View, |
||||
HelperText: Text, |
||||
}); |
||||
|
||||
cssInterop(PrimitiveIcon, { |
||||
className: { |
||||
target: 'style', |
||||
nativeStyleToProp: { |
||||
height: true, |
||||
width: true, |
||||
fill: true, |
||||
color: true, |
||||
stroke: true, |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
type IFormControlProps = React.ComponentProps<typeof UIFormControl> & |
||||
VariantProps<typeof formControlStyle>; |
||||
|
||||
const FormControl = React.forwardRef< |
||||
React.ComponentRef<typeof UIFormControl>, |
||||
IFormControlProps |
||||
>(function FormControl({ className, size = 'md', ...props }, ref) { |
||||
return ( |
||||
<UIFormControl |
||||
ref={ref} |
||||
className={formControlStyle({ size, class: className })} |
||||
{...props} |
||||
context={{ size }} |
||||
/> |
||||
); |
||||
}); |
||||
|
||||
type IFormControlErrorProps = React.ComponentProps<typeof UIFormControl.Error> & |
||||
VariantProps<typeof formControlErrorStyle>; |
||||
|
||||
const FormControlError = React.forwardRef< |
||||
React.ComponentRef<typeof UIFormControl.Error>, |
||||
IFormControlErrorProps |
||||
>(function FormControlError({ className, ...props }, ref) { |
||||
return ( |
||||
<UIFormControl.Error |
||||
ref={ref} |
||||
className={formControlErrorStyle({ class: className })} |
||||
{...props} |
||||
/> |
||||
); |
||||
}); |
||||
|
||||
type IFormControlErrorTextProps = React.ComponentProps< |
||||
typeof UIFormControl.Error.Text |
||||
> & |
||||
VariantProps<typeof formControlErrorTextStyle>; |
||||
|
||||
const FormControlErrorText = React.forwardRef< |
||||
React.ComponentRef<typeof UIFormControl.Error.Text>, |
||||
IFormControlErrorTextProps |
||||
>(function FormControlErrorText({ className, size, ...props }, ref) { |
||||
const { size: parentSize } = useStyleContext(SCOPE); |
||||
return ( |
||||
<UIFormControl.Error.Text |
||||
className={formControlErrorTextStyle({ |
||||
parentVariants: { size: parentSize }, |
||||
size, |
||||
class: className, |
||||
})} |
||||
ref={ref} |
||||
{...props} |
||||
/> |
||||
); |
||||
}); |
||||
|
||||
type IFormControlErrorIconProps = React.ComponentProps< |
||||
typeof UIFormControl.Error.Icon |
||||
> & |
||||
VariantProps<typeof formControlErrorIconStyle> & { |
||||
height?: number; |
||||
width?: number; |
||||
}; |
||||
|
||||
const FormControlErrorIcon = React.forwardRef< |
||||
React.ComponentRef<typeof UIFormControl.Error.Icon>, |
||||
IFormControlErrorIconProps |
||||
>(function FormControlErrorIcon({ className, size, ...props }, ref) { |
||||
const { size: parentSize } = useStyleContext(SCOPE); |
||||
|
||||
if (typeof size === 'number') { |
||||
return ( |
||||
<UIFormControl.Error.Icon |
||||
ref={ref} |
||||
{...props} |
||||
className={formControlErrorIconStyle({ class: className })} |
||||
size={size} |
||||
/> |
||||
); |
||||
} else if ( |
||||
(props.height !== undefined || props.width !== undefined) && |
||||
size === undefined |
||||
) { |
||||
return ( |
||||
<UIFormControl.Error.Icon |
||||
ref={ref} |
||||
{...props} |
||||
className={formControlErrorIconStyle({ class: className })} |
||||
/> |
||||
); |
||||
} |
||||
return ( |
||||
<UIFormControl.Error.Icon |
||||
className={formControlErrorIconStyle({ |
||||
parentVariants: { size: parentSize }, |
||||
size, |
||||
class: className, |
||||
})} |
||||
{...props} |
||||
/> |
||||
); |
||||
}); |
||||
|
||||
type IFormControlLabelProps = React.ComponentProps<typeof UIFormControl.Label> & |
||||
VariantProps<typeof formControlLabelStyle>; |
||||
|
||||
const FormControlLabel = React.forwardRef< |
||||
React.ComponentRef<typeof UIFormControl.Label>, |
||||
IFormControlLabelProps |
||||
>(function FormControlLabel({ className, ...props }, ref) { |
||||
return ( |
||||
<UIFormControl.Label |
||||
ref={ref} |
||||
className={formControlLabelStyle({ class: className })} |
||||
{...props} |
||||
/> |
||||
); |
||||
}); |
||||
|
||||
type IFormControlLabelTextProps = React.ComponentProps< |
||||
typeof UIFormControl.Label.Text |
||||
> & |
||||
VariantProps<typeof formControlLabelTextStyle>; |
||||
|
||||
const FormControlLabelText = React.forwardRef< |
||||
React.ComponentRef<typeof UIFormControl.Label.Text>, |
||||
IFormControlLabelTextProps |
||||
>(function FormControlLabelText({ className, size, ...props }, ref) { |
||||
const { size: parentSize } = useStyleContext(SCOPE); |
||||
|
||||
return ( |
||||
<UIFormControl.Label.Text |
||||
className={formControlLabelTextStyle({ |
||||
parentVariants: { size: parentSize }, |
||||
size, |
||||
class: className, |
||||
})} |
||||
ref={ref} |
||||
{...props} |
||||
/> |
||||
); |
||||
}); |
||||
|
||||
type IFormControlHelperProps = React.ComponentProps< |
||||
typeof UIFormControl.Helper |
||||
> & |
||||
VariantProps<typeof formControlHelperStyle>; |
||||
|
||||
const FormControlHelper = React.forwardRef< |
||||
React.ComponentRef<typeof UIFormControl.Helper>, |
||||
IFormControlHelperProps |
||||
>(function FormControlHelper({ className, ...props }, ref) { |
||||
return ( |
||||
<UIFormControl.Helper |
||||
ref={ref} |
||||
className={formControlHelperStyle({ |
||||
class: className, |
||||
})} |
||||
{...props} |
||||
/> |
||||
); |
||||
}); |
||||
|
||||
type IFormControlHelperTextProps = React.ComponentProps< |
||||
typeof UIFormControl.Helper.Text |
||||
> & |
||||
VariantProps<typeof formControlHelperTextStyle>; |
||||
|
||||
const FormControlHelperText = React.forwardRef< |
||||
React.ComponentRef<typeof UIFormControl.Helper.Text>, |
||||
IFormControlHelperTextProps |
||||
>(function FormControlHelperText({ className, size, ...props }, ref) { |
||||
const { size: parentSize } = useStyleContext(SCOPE); |
||||
|
||||
return ( |
||||
<UIFormControl.Helper.Text |
||||
className={formControlHelperTextStyle({ |
||||
parentVariants: { size: parentSize }, |
||||
size, |
||||
class: className, |
||||
})} |
||||
ref={ref} |
||||
{...props} |
||||
/> |
||||
); |
||||
}); |
||||
|
||||
FormControl.displayName = 'FormControl'; |
||||
FormControlError.displayName = 'FormControlError'; |
||||
FormControlErrorText.displayName = 'FormControlErrorText'; |
||||
FormControlErrorIcon.displayName = 'FormControlErrorIcon'; |
||||
FormControlLabel.displayName = 'FormControlLabel'; |
||||
FormControlLabelText.displayName = 'FormControlLabelText'; |
||||
FormControlLabelAstrick.displayName = 'FormControlLabelAstrick'; |
||||
FormControlHelper.displayName = 'FormControlHelper'; |
||||
FormControlHelperText.displayName = 'FormControlHelperText'; |
||||
|
||||
export { |
||||
FormControl, |
||||
FormControlError, |
||||
FormControlErrorText, |
||||
FormControlErrorIcon, |
||||
FormControlLabel, |
||||
FormControlLabelText, |
||||
FormControlLabelAstrick, |
||||
FormControlHelper, |
||||
FormControlHelperText, |
||||
}; |
@ -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', |
||||
}, |
||||
}, |
||||
}); |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue