From 55dcfb06df5cdf520cb316b3af6e73650063e52f Mon Sep 17 00:00:00 2001 From: Innei Date: Thu, 23 Jan 2025 18:35:55 +0800 Subject: [PATCH] fix: correct ModalHeaderSubmitButton naming and enhance UI components - Renamed `ModalHeaderShubmitButton` to `ModalHeaderSubmitButton` for consistency. - Updated `SwipeableItem` to include animated text for action labels. - Refactored `TextField` to support an input postfix element and improved styling. - Enhanced `GroupedInsetListCard` to conditionally show separators between children. - Adjusted `MarkdownWeb` to prevent vertical overflow. - Updated `FollowFeed`, `FollowList`, and `rsshub-form` to use the corrected `ModalHeaderSubmitButton`. - Added navigation functionality to the `AddListButton` in `ListsScreen`. - Improved color validation in utility functions. These changes improve code consistency and enhance the user interface across various components. Signed-off-by: Innei --- .../common/ModalSharedComponents.tsx | 9 +- .../src/components/common/SwipeableItem.tsx | 14 +- .../src/components/ui/form/TextField.tsx | 55 +++++- .../src/components/ui/grouped/GroupedList.tsx | 50 +++--- .../components/ui/typography/MarkdownWeb.tsx | 2 +- apps/mobile/src/modules/feed/FollowFeed.tsx | 4 +- .../mobile/src/modules/feed/view-selector.tsx | 9 +- apps/mobile/src/modules/list/FollowList.tsx | 4 +- .../src/modules/settings/routes/Lists.tsx | 5 +- apps/mobile/src/screens/(modal)/_layout.tsx | 6 + apps/mobile/src/screens/(modal)/list.tsx | 162 ++++++++++++++++++ .../src/screens/(modal)/rsshub-form.tsx | 22 ++- packages/utils/src/color.ts | 2 +- 13 files changed, 282 insertions(+), 62 deletions(-) create mode 100644 apps/mobile/src/screens/(modal)/list.tsx diff --git a/apps/mobile/src/components/common/ModalSharedComponents.tsx b/apps/mobile/src/components/common/ModalSharedComponents.tsx index f6dcc3160..af3df0adc 100644 --- a/apps/mobile/src/components/common/ModalSharedComponents.tsx +++ b/apps/mobile/src/components/common/ModalSharedComponents.tsx @@ -30,16 +30,16 @@ const ModalHeaderCloseButtonImpl = () => { ) } -export interface ModalHeaderShubmitButtonProps { +export interface ModalHeaderSubmitButtonProps { isValid: boolean onPress: () => void isLoading?: boolean } -export const ModalHeaderShubmitButton = ({ +export const ModalHeaderSubmitButton = ({ isValid, onPress, isLoading, -}: ModalHeaderShubmitButtonProps) => { +}: ModalHeaderSubmitButtonProps) => { return } @@ -47,8 +47,9 @@ const ModalHeaderShubmitButtonImpl = ({ isValid, onPress, isLoading, -}: ModalHeaderShubmitButtonProps) => { +}: ModalHeaderSubmitButtonProps) => { const label = useColor("label") + return ( {isLoading ? ( diff --git a/apps/mobile/src/components/common/SwipeableItem.tsx b/apps/mobile/src/components/common/SwipeableItem.tsx index e6f53d6ed..1e064afe7 100644 --- a/apps/mobile/src/components/common/SwipeableItem.tsx +++ b/apps/mobile/src/components/common/SwipeableItem.tsx @@ -118,6 +118,10 @@ export const SwipeableItem: React.FC = ({ const renderRightActions = (progress: Animated.AnimatedInterpolation) => { const width = rightActions?.length ? rightActions.length * 74 : 74 + const parallaxX = progress.interpolate({ + inputRange: [0, 1, 1.2], + outputRange: [0, 0, 10], + }) return ( <> = ({ onPress={action.onPress} > {action.icon} - {action.label} + + {action.label} + ) diff --git a/apps/mobile/src/components/ui/form/TextField.tsx b/apps/mobile/src/components/ui/form/TextField.tsx index 2ee4ca30b..fd04e30ea 100644 --- a/apps/mobile/src/components/ui/form/TextField.tsx +++ b/apps/mobile/src/components/ui/form/TextField.tsx @@ -7,25 +7,36 @@ import { accentColor } from "@/src/theme/colors" import { FormLabel } from "./Label" -interface TextFieldProps { +interface BaseFieldProps { wrapperClassName?: string wrapperStyle?: StyleProp - label?: string description?: string required?: boolean + + inputPostfixElement?: React.ReactNode } -export const TextField = forwardRef( +const BaseField = forwardRef( ( - { className, style, wrapperClassName, wrapperStyle, label, description, required, ...rest }, + { + className, + style, + wrapperClassName, + wrapperStyle, + label, + description, + required, + inputPostfixElement, + ...rest + }, ref, ) => { return ( - <> - {!!label && } + + {!!label && } {!!description && ( - {description} + {description} )} ( + {inputPostfixElement} - + ) }, ) +export const TextField = forwardRef((props, ref) => ( + +)) + +interface NumberFieldProps extends BaseFieldProps { + value?: number + onChangeNumber?: (value: number) => void + defaultValue?: number +} + +export const NumberField = forwardRef< + TextInput, + Omit & + NumberFieldProps +>(({ value, onChangeNumber, defaultValue, ...rest }, ref) => ( + onChangeNumber?.(Math.min(Number(text), Number.MAX_SAFE_INTEGER))} + defaultValue={defaultValue?.toString()} + /> +)) + const styles = StyleSheet.create({ textField: { fontSize: 16, diff --git a/apps/mobile/src/components/ui/grouped/GroupedList.tsx b/apps/mobile/src/components/ui/grouped/GroupedList.tsx index 552e4b276..bd9cc69f6 100644 --- a/apps/mobile/src/components/ui/grouped/GroupedList.tsx +++ b/apps/mobile/src/components/ui/grouped/GroupedList.tsx @@ -9,11 +9,13 @@ import { MingcuteRightLine } from "@/src/icons/mingcute_right_line" import { RightCuteReIcon } from "@/src/icons/right_cute_re" import { useColor } from "@/src/theme/colors" -export const GroupedInsetListCard: FC = ({ - children, - className, - ...props -}) => { +type GroupedInsetListCardProps = { + showSeparator?: boolean +} + +export const GroupedInsetListCard: FC< + PropsWithChildren & ViewProps & GroupedInsetListCardProps +> = ({ children, className, showSeparator = true, ...props }) => { return ( = ({ className, )} > - {React.Children.map(children, (child, index) => { - const isLast = index === React.Children.count(children) - 1 + {showSeparator + ? React.Children.map(children, (child, index) => { + const isLast = index === React.Children.count(children) - 1 - const isNavigationLink = - React.isValidElement(child) && - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - (child.type as Function).name === GroupedInsetListNavigationLink.name + const isNavigationLink = + React.isValidElement(child) && + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + (child.type as Function).name === GroupedInsetListNavigationLink.name - return ( - - {child} - {!isLast && ( - - )} - - ) - })} + return ( + + {child} + {!isLast && ( + + )} + + ) + }) + : children} ) } diff --git a/apps/mobile/src/components/ui/typography/MarkdownWeb.tsx b/apps/mobile/src/components/ui/typography/MarkdownWeb.tsx index e50ea7988..03b86c0fa 100644 --- a/apps/mobile/src/components/ui/typography/MarkdownWeb.tsx +++ b/apps/mobile/src/components/ui/typography/MarkdownWeb.tsx @@ -34,7 +34,7 @@ const MarkdownWeb: WebComponent<{ overscroll-behavior: none; } body, html { - overflow: hidden; + overflow-y: hidden; } `, }} diff --git a/apps/mobile/src/modules/feed/FollowFeed.tsx b/apps/mobile/src/modules/feed/FollowFeed.tsx index d7fa06ae3..73348aaed 100644 --- a/apps/mobile/src/modules/feed/FollowFeed.tsx +++ b/apps/mobile/src/modules/feed/FollowFeed.tsx @@ -10,7 +10,7 @@ import { z } from "zod" import { ModalHeaderCloseButton, - ModalHeaderShubmitButton, + ModalHeaderSubmitButton, } from "@/src/components/common/ModalSharedComponents" import { FormProvider } from "@/src/components/ui/form/FormProvider" import { FormLabel } from "@/src/components/ui/form/Label" @@ -108,7 +108,7 @@ function FollowImpl() { headerLeft: ModalHeaderCloseButton, gestureEnabled: !isDirty, headerRight: () => ( - {view.name} diff --git a/apps/mobile/src/modules/list/FollowList.tsx b/apps/mobile/src/modules/list/FollowList.tsx index 60a26ae27..687158a21 100644 --- a/apps/mobile/src/modules/list/FollowList.tsx +++ b/apps/mobile/src/modules/list/FollowList.tsx @@ -8,7 +8,7 @@ import { z } from "zod" import { ModalHeaderCloseButton, - ModalHeaderShubmitButton, + ModalHeaderSubmitButton, } from "@/src/components/common/ModalSharedComponents" import { FormProvider } from "@/src/components/ui/form/FormProvider" import { FormLabel } from "@/src/components/ui/form/Label" @@ -118,7 +118,7 @@ const Impl = (props: { id: string }) => { headerLeft: ModalHeaderCloseButton, gestureEnabled: !isDirty, headerRight: () => ( - { const AddListButton = () => { const labelColor = useColor("label") return ( - + router.push("/list")}> ) @@ -109,7 +110,7 @@ const ListItemCell: ListRenderItem = ({ item: list { label: "Edit", onPress: () => { - // TODO + router.push(`/list?id=${list.id}`) }, backgroundColor: "#0ea5e9", }, diff --git a/apps/mobile/src/screens/(modal)/_layout.tsx b/apps/mobile/src/screens/(modal)/_layout.tsx index 556f4460e..aaf78fa4d 100644 --- a/apps/mobile/src/screens/(modal)/_layout.tsx +++ b/apps/mobile/src/screens/(modal)/_layout.tsx @@ -21,6 +21,12 @@ export default function ModalLayout() { title: "Follow", }} /> + ) } diff --git a/apps/mobile/src/screens/(modal)/list.tsx b/apps/mobile/src/screens/(modal)/list.tsx new file mode 100644 index 000000000..ba31735ba --- /dev/null +++ b/apps/mobile/src/screens/(modal)/list.tsx @@ -0,0 +1,162 @@ +import { FeedViewType } from "@follow/constants" +import { zodResolver } from "@hookform/resolvers/zod" +import { Stack, useLocalSearchParams } from "expo-router" +import { memo } from "react" +import { Controller, useForm } from "react-hook-form" +import { View } from "react-native" +import { KeyboardAwareScrollView } from "react-native-keyboard-controller" +import { z } from "zod" + +import { + ModalHeaderCloseButton, + ModalHeaderSubmitButton, +} from "@/src/components/common/ModalSharedComponents" +import { FormProvider, useFormContext } from "@/src/components/ui/form/FormProvider" +import { FormLabel } from "@/src/components/ui/form/Label" +import { NumberField, TextField } from "@/src/components/ui/form/TextField" +import { GroupedInsetListCard } from "@/src/components/ui/grouped/GroupedList" +import { PowerIcon } from "@/src/icons/power" +import { FeedViewSelector } from "@/src/modules/feed/view-selector" +import { useList } from "@/src/store/list/hooks" +import { accentColor } from "@/src/theme/colors" + +const listSchema = z.object({ + title: z.string().min(1), + description: z.string().min(1), + image: z.string().url(), + fee: z.number().min(0), + view: z.nativeEnum(FeedViewType), +}) +export default function ListScreen() { + const listId = useLocalSearchParams<{ id?: string }>().id + + const list = useList(listId || "") + const form = useForm({ + defaultValues: list, + resolver: zodResolver(listSchema), + mode: "all", + }) + return ( + + + + + + ( + + )} + /> + + + ( + + )} + /> + + + + ( + + )} + /> + + + + + ( + + )} + /> + + + + ( + } + ref={ref} + /> + )} + /> + + + + + ) +} + +interface ScreenOptionsProps { + title?: string +} +const ScreenOptions = memo(({ title }: ScreenOptionsProps) => { + const form = useFormContext() + + return ( + ( + + {}} /> + + ), + + headerTitle: title ? `Edit List - ${title}` : "Create List", + }} + /> + ) +}) diff --git a/apps/mobile/src/screens/(modal)/rsshub-form.tsx b/apps/mobile/src/screens/(modal)/rsshub-form.tsx index c72abe4c0..77544ad63 100644 --- a/apps/mobile/src/screens/(modal)/rsshub-form.tsx +++ b/apps/mobile/src/screens/(modal)/rsshub-form.tsx @@ -18,7 +18,7 @@ import { z } from "zod" import { HeaderTitleExtra } from "@/src/components/common/HeaderTitleExtra" import { ModalHeaderCloseButton, - ModalHeaderShubmitButton, + ModalHeaderSubmitButton, } from "@/src/components/common/ModalSharedComponents" import { FormProvider, useFormContext } from "@/src/components/ui/form/FormProvider" import { Select } from "@/src/components/ui/form/Select" @@ -247,7 +247,7 @@ const ScreenOptions = memo(({ name, routeName, route, routePrefix }: ScreenOptio headerRight: () => ( - + ), @@ -267,17 +267,15 @@ const Title = ({ name, routeName, route, routePrefix }: ScreenOptionsProps) => { ) } -type ModalHeaderSubmitButtonProps = { - routePrefix: string - route: string -} -const ModalHeaderSubmitButton = ({ routePrefix, route }: ModalHeaderSubmitButtonProps) => { - return -} - const routeParamsKeyPrefix = "route-params-" -const ModalHeaderSubmitButtonImpl = ({ routePrefix, route }: ModalHeaderSubmitButtonProps) => { +const ModalHeaderSubmitButtonImpl = ({ + routePrefix, + route, +}: { + routePrefix: string + route: string +}) => { const form = useFormContext() const { isValid } = form.formState @@ -339,5 +337,5 @@ const ModalHeaderSubmitButtonImpl = ({ routePrefix, route }: ModalHeaderSubmitBu } }) - return + return } diff --git a/packages/utils/src/color.ts b/packages/utils/src/color.ts index e6032e3e2..2bc50a289 100644 --- a/packages/utils/src/color.ts +++ b/packages/utils/src/color.ts @@ -148,7 +148,7 @@ export const isRGBColor = (color: string) => { return /^rgb\(\d{1,3},\s*\d{1,3},\s*\d{1,3}\)$/.test(color) } export const isRGBAColor = (color: string) => { - return /^rgba\(\d{1,3},\s*\d{1,3},\s*\d{1,3},\s*0?\.\d+\)$/.test(color) + return /^rgba\(\d{1,3},\s*\d{1,3},\s*\d{1,3},\s*(?:0?\.\d+|1(?:\.0+)?)\)$/.test(color) } export const withOpacity = (color: string, opacity: number) => {