Skip to content

Commit

Permalink
fix(rn): rsshub form styles
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <[email protected]>
  • Loading branch information
Innei committed Jan 3, 2025
1 parent cf7259b commit 8583210
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 52 deletions.
94 changes: 94 additions & 0 deletions apps/mobile/src/components/ui/form/PickerIos.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/* eslint-disable @eslint-react/no-array-index-key */
import { cn } from "@follow/utils"
import { Portal } from "@gorhom/portal"
import { Picker } from "@react-native-picker/picker"
import { useMemo, useState } from "react"
import type { StyleProp, ViewStyle } from "react-native"
import { Pressable, Text, View } from "react-native"
import Animated, { SlideOutDown } from "react-native-reanimated"
import { useEventCallback } from "usehooks-ts"

import { MingcuteDownLineIcon } from "@/src/icons/mingcute_down_line"
import { useColor } from "@/src/theme/colors"

import { BlurEffect } from "../../common/HeaderBlur"

interface PickerIosProps<T> {
options: { label: string; value: T }[]

value: T
onValueChange: (value: T) => void

wrapperClassName?: string
wrapperStyle?: StyleProp<ViewStyle>
}
export function PickerIos<T>({
options,
value,
onValueChange,
wrapperClassName,
wrapperStyle,
}: PickerIosProps<T>) {
const [isOpen, setIsOpen] = useState(false)

const [currentValue, setCurrentValue] = useState(() => {
if (!value) {
return options[0].value
}
return value
})

const valueToLabelMap = useMemo(() => {
return options.reduce((acc, option) => {
acc.set(option.value, option.label)
return acc
}, new Map<T, string>())
}, [options])

const handleChangeValue = useEventCallback((value: T) => {
setCurrentValue(value)
onValueChange(value)
})

const systemFill = useColor("text")

return (
<>
{/* Trigger */}
<Pressable onPress={() => setIsOpen(!isOpen)}>
<View
className={cn(
"border-system-fill/80 bg-system-fill/30 h-10 flex-row items-center rounded-lg border pl-4 pr-2",
wrapperClassName,
)}
style={wrapperStyle}
>
<Text className="text-text">{valueToLabelMap.get(currentValue)}</Text>
<View className="ml-auto shrink-0">
<MingcuteDownLineIcon color={systemFill} height={16} width={16} />
</View>
</View>
</Pressable>
{/* Picker */}
{isOpen && (
<Portal>
<Pressable
onPress={() => setIsOpen(false)}
className="absolute inset-0 flex flex-row items-end"
>
<Animated.View className="relative flex-1" exiting={SlideOutDown}>
<BlurEffect />
<Pressable onPress={(e) => e.stopPropagation()}>
<Picker selectedValue={currentValue} onValueChange={handleChangeValue}>
{options.map((option, index) => (
<Picker.Item key={index} label={option.label} value={option.value} />
))}
</Picker>
</Pressable>
</Animated.View>
</Pressable>
</Portal>
)}
</>
)
}
60 changes: 26 additions & 34 deletions apps/mobile/src/components/ui/form/Select.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
/* eslint-disable @eslint-react/no-array-index-key */
import { cn } from "@follow/utils"
import { Portal } from "@gorhom/portal"
import { Picker } from "@react-native-picker/picker"
import { useMemo, useState } from "react"
import type { StyleProp, ViewStyle } from "react-native"
import { Pressable, Text, View } from "react-native"
import Animated, { SlideOutDown } from "react-native-reanimated"
import { Text, View } from "react-native"
import ContextMenu from "react-native-context-menu-view"
import { useEventCallback } from "usehooks-ts"

import { MingcuteDownLineIcon } from "@/src/icons/mingcute_down_line"
import { useColor } from "@/src/theme/colors"

import { BlurEffect } from "../../common/HeaderBlur"
import { FormLabel } from "./Label"

interface SelectProps<T> {
options: { label: string; value: T }[]
Expand All @@ -21,16 +18,17 @@ interface SelectProps<T> {

wrapperClassName?: string
wrapperStyle?: StyleProp<ViewStyle>

label?: string
}
export function Select<T>({
options,
value,
onValueChange,
wrapperClassName,
wrapperStyle,
label,
}: SelectProps<T>) {
const [isOpen, setIsOpen] = useState(false)

const [currentValue, setCurrentValue] = useState(() => {
if (!value) {
return options[0].value
Expand All @@ -51,43 +49,37 @@ export function Select<T>({
})

const systemFill = useColor("text")

return (
<>
<View className="w-full flex-1 flex-row items-center">
{!!label && <FormLabel className="pl-1" label={label} />}
<View className="flex-1" />
{/* Trigger */}
<Pressable onPress={() => setIsOpen(!isOpen)}>
<ContextMenu
dropdownMenuMode
actions={options.map((option) => ({
title: option.label,
selected: option.value === currentValue,
}))}
onPress={(e) => {
const { index } = e.nativeEvent
handleChangeValue(options[index].value)
}}
>
<View
className={cn(
"border-system-fill/80 bg-system-fill/30 h-10 flex-row items-center rounded-lg border pl-4 pr-2",
"border-system-fill/80 bg-system-fill/30 h-8 flex-row items-center rounded-lg border pl-3 pr-2",
"min-w-[80px]",
wrapperClassName,
)}
style={wrapperStyle}
>
<Text className="text-text">{valueToLabelMap.get(currentValue)}</Text>
<View className="ml-auto shrink-0">
<View className="ml-auto shrink-0 pl-2">
<MingcuteDownLineIcon color={systemFill} height={16} width={16} />
</View>
</View>
</Pressable>
{/* Picker */}
{isOpen && (
<Portal>
<Pressable
onPress={() => setIsOpen(false)}
className="absolute inset-0 flex flex-row items-end"
>
<Animated.View className="relative flex-1" exiting={SlideOutDown}>
<BlurEffect />
<Pressable onPress={(e) => e.stopPropagation()}>
<Picker selectedValue={currentValue} onValueChange={handleChangeValue}>
{options.map((option, index) => (
<Picker.Item key={index} label={option.label} value={option.value} />
))}
</Picker>
</Pressable>
</Animated.View>
</Pressable>
</Portal>
)}
</>
</ContextMenu>
</View>
)
}
38 changes: 23 additions & 15 deletions apps/mobile/src/components/ui/form/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,36 @@ import { forwardRef } from "react"
import type { StyleProp, TextInputProps, ViewStyle } from "react-native"
import { StyleSheet, TextInput, View } from "react-native"

import { FormLabel } from "./Label"

interface TextFieldProps {
wrapperClassName?: string
wrapperStyle?: StyleProp<ViewStyle>

label?: string
required?: boolean
}

export const TextField = forwardRef<TextInput, TextInputProps & TextFieldProps>(
({ className, style, wrapperClassName, wrapperStyle, ...rest }, ref) => {
({ className, style, wrapperClassName, wrapperStyle, label, required, ...rest }, ref) => {
return (
<View
className={cn(
"bg-system-fill/40 relative h-10 flex-row items-center rounded-lg px-4",
wrapperClassName,
)}
style={wrapperStyle}
>
<TextInput
ref={ref}
className={cn("text-text placeholder:text-placeholder-text w-full flex-1", className)}
style={StyleSheet.flatten([styles.textField, style])}
{...rest}
/>
</View>
<>
{!!label && <FormLabel className="pl-1" label={label} optional={!required} />}
<View
className={cn(
"bg-system-fill/40 relative h-10 flex-row items-center rounded-lg px-4",
wrapperClassName,
)}
style={wrapperStyle}
>
<TextInput
ref={ref}
className={cn("text-text placeholder:text-placeholder-text w-full flex-1", className)}
style={StyleSheet.flatten([styles.textField, style])}
{...rest}
/>
</View>
</>
)
},
)
Expand Down
7 changes: 4 additions & 3 deletions apps/mobile/src/screens/(modal)/rsshub-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { z } from "zod"
import { HeaderTitleExtra } from "@/src/components/common/HeaderTitleExtra"
import { ModalHeaderCloseButton } from "@/src/components/common/ModalSharedComponents"
import { FormProvider, useFormContext } from "@/src/components/ui/form/FormProvider"
import { FormLabel } from "@/src/components/ui/form/Label"
import { Select } from "@/src/components/ui/form/Select"
import { TextField } from "@/src/components/ui/form/TextField"
import MarkdownWeb from "@/src/components/ui/typography/MarkdownWeb"
Expand Down Expand Up @@ -121,7 +120,6 @@ function FormImpl({ route, routePrefix, name }: RsshubFormParams) {

return (
<View key={keyItem.name}>
<FormLabel className="pl-1" label={keyItem.name} optional={keyItem.optional} />
{!parameters?.options && (
<Controller
name={keyItem.name}
Expand All @@ -136,6 +134,8 @@ function FormImpl({ route, routePrefix, name }: RsshubFormParams) {
}}
render={({ field: { onChange, onBlur, ref, value } }) => (
<TextField
label={keyItem.name}
required={!keyItem.optional}
wrapperClassName="mt-2"
placeholder={formPlaceholder[keyItem.name]}
onBlur={onBlur}
Expand All @@ -150,6 +150,7 @@ function FormImpl({ route, routePrefix, name }: RsshubFormParams) {

{!!parameters?.options && (
<Select
label={keyItem.name}
wrapperClassName="mt-2"
options={parameters.options}
value={form.getValues(keyItem.name)}
Expand All @@ -164,7 +165,7 @@ function FormImpl({ route, routePrefix, name }: RsshubFormParams) {
)}

{!!parameters && (
<Text className="text-text/80 ml-2 mt-2 text-xs">{parameters.description}</Text>
<Text className="text-text/80 ml-1 mt-2 text-xs">{parameters.description}</Text>
)}
</View>
)
Expand Down

0 comments on commit 8583210

Please sign in to comment.