diff --git a/packages/frontend/admin/src/components/ui/carousel.tsx b/packages/frontend/admin/src/components/ui/carousel.tsx index f085bf0150..1880103d40 100644 --- a/packages/frontend/admin/src/components/ui/carousel.tsx +++ b/packages/frontend/admin/src/components/ui/carousel.tsx @@ -4,7 +4,15 @@ import useEmblaCarousel, { type UseEmblaCarouselType, } from 'embla-carousel-react'; import { ArrowLeft, ArrowRight } from 'lucide-react'; -import * as React from 'react'; +import { + createContext, + forwardRef, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from 'react'; type CarouselApi = UseEmblaCarouselType[1]; type UseCarouselParameters = Parameters; @@ -27,10 +35,10 @@ type CarouselContextProps = { canScrollNext: boolean; } & CarouselProps; -const CarouselContext = React.createContext(null); +const CarouselContext = createContext(null); function useCarousel() { - const context = React.useContext(CarouselContext); + const context = useContext(CarouselContext); if (!context) { throw new Error('useCarousel must be used within a '); @@ -39,7 +47,7 @@ function useCarousel() { return context; } -const Carousel = React.forwardRef< +const Carousel = forwardRef< HTMLDivElement, React.HTMLAttributes & CarouselProps >( @@ -62,10 +70,10 @@ const Carousel = React.forwardRef< }, plugins ); - const [canScrollPrev, setCanScrollPrev] = React.useState(false); - const [canScrollNext, setCanScrollNext] = React.useState(false); + const [canScrollPrev, setCanScrollPrev] = useState(false); + const [canScrollNext, setCanScrollNext] = useState(false); - const onSelect = React.useCallback((api: CarouselApi) => { + const onSelect = useCallback((api: CarouselApi) => { if (!api) { return; } @@ -74,15 +82,15 @@ const Carousel = React.forwardRef< setCanScrollNext(api.canScrollNext()); }, []); - const scrollPrev = React.useCallback(() => { + const scrollPrev = useCallback(() => { api?.scrollPrev(); }, [api]); - const scrollNext = React.useCallback(() => { + const scrollNext = useCallback(() => { api?.scrollNext(); }, [api]); - const handleKeyDown = React.useCallback( + const handleKeyDown = useCallback( (event: React.KeyboardEvent) => { if (event.key === 'ArrowLeft') { event.preventDefault(); @@ -95,7 +103,7 @@ const Carousel = React.forwardRef< [scrollPrev, scrollNext] ); - React.useEffect(() => { + useEffect(() => { if (!api || !setApi) { return; } @@ -103,7 +111,7 @@ const Carousel = React.forwardRef< setApi(api); }, [api, setApi]); - React.useEffect(() => { + useEffect(() => { if (!api) { return; } @@ -117,20 +125,33 @@ const Carousel = React.forwardRef< }; }, [api, onSelect]); + const resolvedOrientation = + orientation || (opts?.axis === 'y' ? 'vertical' : 'horizontal'); + const carouselContextValue = useMemo( + () => ({ + carouselRef, + api, + opts, + orientation: resolvedOrientation, + scrollPrev, + scrollNext, + canScrollPrev, + canScrollNext, + }), + [ + api, + canScrollNext, + canScrollPrev, + carouselRef, + opts, + resolvedOrientation, + scrollNext, + scrollPrev, + ] + ); + return ( - +
>(({ className, ...props }, ref) => { @@ -169,7 +190,7 @@ const CarouselContent = React.forwardRef< }); CarouselContent.displayName = 'CarouselContent'; -const CarouselItem = React.forwardRef< +const CarouselItem = forwardRef< HTMLDivElement, React.HTMLAttributes >(({ className, ...props }, ref) => { @@ -191,7 +212,7 @@ const CarouselItem = React.forwardRef< }); CarouselItem.displayName = 'CarouselItem'; -const CarouselPrevious = React.forwardRef< +const CarouselPrevious = forwardRef< HTMLButtonElement, React.ComponentProps >(({ className, variant = 'outline', size = 'icon', ...props }, ref) => { @@ -220,7 +241,7 @@ const CarouselPrevious = React.forwardRef< }); CarouselPrevious.displayName = 'CarouselPrevious'; -const CarouselNext = React.forwardRef< +const CarouselNext = forwardRef< HTMLButtonElement, React.ComponentProps >(({ className, variant = 'outline', size = 'icon', ...props }, ref) => { diff --git a/packages/frontend/admin/src/components/ui/chart.tsx b/packages/frontend/admin/src/components/ui/chart.tsx index 38ed8f1d34..9d40c54ed8 100644 --- a/packages/frontend/admin/src/components/ui/chart.tsx +++ b/packages/frontend/admin/src/components/ui/chart.tsx @@ -1,5 +1,5 @@ import { cn } from '@affine/admin/utils'; -import * as React from 'react'; +import { createContext, forwardRef, useContext, useId, useMemo } from 'react'; import type { TooltipProps } from 'recharts'; import { ResponsiveContainer, Tooltip as RechartsTooltip } from 'recharts'; @@ -18,10 +18,10 @@ type ChartContextValue = { config: ChartConfig; }; -const ChartContext = React.createContext(null); +const ChartContext = createContext(null); function useChart() { - const value = React.useContext(ChartContext); + const value = useContext(ChartContext); if (!value) { throw new Error('useChart must be used within '); } @@ -75,13 +75,14 @@ type ChartContainerProps = React.ComponentProps<'div'> & { children: React.ComponentProps['children']; }; -const ChartContainer = React.forwardRef( +const ChartContainer = forwardRef( ({ id, className, children, config, ...props }, ref) => { - const uniqueId = React.useId(); + const uniqueId = useId(); const chartId = `chart-${id ?? uniqueId.replace(/:/g, '')}`; + const chartContextValue = useMemo(() => ({ config }), [config]); return ( - +
React.ReactNode; }; -const ChartTooltipContent = React.forwardRef< - HTMLDivElement, - TooltipContentProps ->(({ active, payload, label, labelFormatter, valueFormatter }, ref) => { - const { config } = useChart(); +const ChartTooltipContent = forwardRef( + ({ active, payload, label, labelFormatter, valueFormatter }, ref) => { + const { config } = useChart(); - if (!active || !payload?.length) { - return null; - } + if (!active || !payload?.length) { + return null; + } - const title = labelFormatter ? labelFormatter(label ?? '', payload) : label; + const title = labelFormatter ? labelFormatter(label ?? '', payload) : label; - return ( -
- {title ? ( -
{title}
- ) : null} -
- {payload.map((item, index) => { - const dataKey = String(item.dataKey ?? item.name ?? index); - const itemConfig = config[dataKey]; - const labelText = itemConfig?.label ?? item.name ?? dataKey; - const numericValue = - typeof item.value === 'number' - ? item.value - : Number(item.value ?? 0); - const valueText = valueFormatter - ? valueFormatter(numericValue, dataKey) - : numericValue; - const color = item.color ?? `var(--color-${dataKey})`; + return ( +
+ {title ? ( +
{title}
+ ) : null} +
+ {payload.map((item, index) => { + const dataKey = String(item.dataKey ?? item.name ?? index); + const itemConfig = config[dataKey]; + const labelText = itemConfig?.label ?? item.name ?? dataKey; + const numericValue = + typeof item.value === 'number' + ? item.value + : Number(item.value ?? 0); + const valueText = valueFormatter + ? valueFormatter(numericValue, dataKey) + : numericValue; + const color = item.color ?? `var(--color-${dataKey})`; - return ( -
-
- ); - })} + return ( +
+
+ ); + })} +
-
- ); -}); + ); + } +); ChartTooltipContent.displayName = 'ChartTooltipContent'; export { ChartContainer, ChartTooltip, ChartTooltipContent }; diff --git a/packages/frontend/admin/src/components/ui/form.tsx b/packages/frontend/admin/src/components/ui/form.tsx index 7277a5c128..af55b45de7 100644 --- a/packages/frontend/admin/src/components/ui/form.tsx +++ b/packages/frontend/admin/src/components/ui/form.tsx @@ -2,7 +2,7 @@ import { Label } from '@affine/admin/components/ui/label'; import { cn } from '@affine/admin/utils'; import type * as LabelPrimitive from '@radix-ui/react-label'; import { Slot } from '@radix-ui/react-slot'; -import * as React from 'react'; +import { createContext, forwardRef, useContext, useId, useMemo } from 'react'; import type { ControllerProps, FieldPath, FieldValues } from 'react-hook-form'; import { Controller, FormProvider, useFormContext } from 'react-hook-form'; @@ -15,7 +15,7 @@ type FormFieldContextValue< name: TName; }; -const FormFieldContext = React.createContext( +const FormFieldContext = createContext( {} as FormFieldContextValue ); @@ -25,16 +25,21 @@ const FormField = < >({ ...props }: ControllerProps) => { + const formFieldContextValue = useMemo( + () => ({ name: props.name }), + [props.name] + ); + return ( - + ); }; const useFormField = () => { - const fieldContext = React.useContext(FormFieldContext); - const itemContext = React.useContext(FormItemContext); + const fieldContext = useContext(FormFieldContext); + const itemContext = useContext(FormItemContext); const { getFieldState, formState } = useFormContext(); const fieldState = getFieldState(fieldContext.name, formState); @@ -59,26 +64,27 @@ type FormItemContextValue = { id: string; }; -const FormItemContext = React.createContext( +const FormItemContext = createContext( {} as FormItemContextValue ); -const FormItem = React.forwardRef< +const FormItem = forwardRef< HTMLDivElement, React.HTMLAttributes >(({ className, ...props }, ref) => { - const id = React.useId(); + const id = useId(); + const formItemContextValue = useMemo(() => ({ id }), [id]); return ( - +
); }); FormItem.displayName = 'FormItem'; -const FormLabel = React.forwardRef< - React.ElementRef, +const FormLabel = forwardRef< + React.ComponentRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => { const { error, formItemId } = useFormField(); @@ -94,8 +100,8 @@ const FormLabel = React.forwardRef< }); FormLabel.displayName = 'FormLabel'; -const FormControl = React.forwardRef< - React.ElementRef, +const FormControl = forwardRef< + React.ComponentRef, React.ComponentPropsWithoutRef >(({ ...props }, ref) => { const { error, formItemId, formDescriptionId, formMessageId } = @@ -117,7 +123,7 @@ const FormControl = React.forwardRef< }); FormControl.displayName = 'FormControl'; -const FormDescription = React.forwardRef< +const FormDescription = forwardRef< HTMLParagraphElement, React.HTMLAttributes >(({ className, ...props }, ref) => { @@ -134,7 +140,7 @@ const FormDescription = React.forwardRef< }); FormDescription.displayName = 'FormDescription'; -const FormMessage = React.forwardRef< +const FormMessage = forwardRef< HTMLParagraphElement, React.HTMLAttributes >(({ className, children, ...props }, ref) => { diff --git a/packages/frontend/admin/src/components/ui/toggle-group.tsx b/packages/frontend/admin/src/components/ui/toggle-group.tsx index f921cffe88..7a5af21ccb 100644 --- a/packages/frontend/admin/src/components/ui/toggle-group.tsx +++ b/packages/frontend/admin/src/components/ui/toggle-group.tsx @@ -2,39 +2,44 @@ import { toggleVariants } from '@affine/admin/components/ui/toggle'; import { cn } from '@affine/admin/utils'; import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group'; import type { VariantProps } from 'class-variance-authority'; -import * as React from 'react'; +import { createContext, forwardRef, useContext, useMemo } from 'react'; -const ToggleGroupContext = React.createContext< - VariantProps ->({ +const ToggleGroupContext = createContext>({ size: 'default', variant: 'default', }); -const ToggleGroup = React.forwardRef< - React.ElementRef, +const ToggleGroup = forwardRef< + React.ComponentRef, React.ComponentPropsWithoutRef & VariantProps ->(({ className, variant, size, children, ...props }, ref) => ( - - - {children} - - -)); +>(({ className, variant, size, children, ...props }, ref) => { + const toggleGroupContextValue = useMemo( + () => ({ variant, size }), + [size, variant] + ); + + return ( + + + {children} + + + ); +}); ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName; -const ToggleGroupItem = React.forwardRef< - React.ElementRef, +const ToggleGroupItem = forwardRef< + React.ComponentRef, React.ComponentPropsWithoutRef & VariantProps >(({ className, children, variant, size, ...props }, ref) => { - const context = React.useContext(ToggleGroupContext); + const context = useContext(ToggleGroupContext); return ( ({ + leftPanel: { + isOpen: leftOpen, + panelContent: leftPanelContent, + setPanelContent: setLeftPanelContent, + togglePanel: toggleLeftPanel, + openPanel: openLeftPanel, + closePanel: closeLeftPanel, + }, + rightPanel: { + isOpen: rightOpen, + panelContent: rightPanelContent, + setPanelContent: handleSetRightPanelContent, + togglePanel: toggleRightPanel, + openPanel: openRightPanel, + closePanel: closeRightPanel, + hasDirtyChanges: rightPanelHasDirtyChanges, + setHasDirtyChanges: setRightPanelHasDirtyChanges, + }, + }), + [ + closeLeftPanel, + closeRightPanel, + handleSetRightPanelContent, + leftOpen, + leftPanelContent, + openLeftPanel, + openRightPanel, + rightOpen, + rightPanelContent, + rightPanelHasDirtyChanges, + setLeftPanelContent, + setRightPanelHasDirtyChanges, + toggleLeftPanel, + toggleRightPanel, + ] + ); return ( - +
diff --git a/packages/frontend/apps/ios/src/modal-config.tsx b/packages/frontend/apps/ios/src/modal-config.tsx index 55d6af9593..8c389f7596 100644 --- a/packages/frontend/apps/ios/src/modal-config.tsx +++ b/packages/frontend/apps/ios/src/modal-config.tsx @@ -2,9 +2,9 @@ import { ModalConfigContext } from '@affine/component'; import { NavigationGestureService } from '@affine/core/mobile/modules/navigation-gesture'; import { globalVars } from '@affine/core/mobile/styles/variables.css'; import { useService } from '@toeverything/infra'; -import { type PropsWithChildren, useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; -export const ModalConfigProvider = ({ children }: PropsWithChildren) => { +export const ModalConfigProvider = ({ children }: React.PropsWithChildren) => { const navigationGesture = useService(NavigationGestureService); const onOpen = useCallback(() => { @@ -17,11 +17,13 @@ export const ModalConfigProvider = ({ children }: PropsWithChildren) => { } return; }, [navigationGesture]); + const modalConfigValue = useMemo( + () => ({ onOpen, dynamicKeyboardHeight: globalVars.appKeyboardHeight }), + [onOpen] + ); return ( - + {children} ); diff --git a/packages/frontend/component/src/ui/menu/mobile/root.tsx b/packages/frontend/component/src/ui/menu/mobile/root.tsx index 3f6083750f..6d86e6aa24 100644 --- a/packages/frontend/component/src/ui/menu/mobile/root.tsx +++ b/packages/frontend/component/src/ui/menu/mobile/root.tsx @@ -7,6 +7,7 @@ import { useContext, useEffect, useImperativeHandle, + useMemo, useState, } from 'react'; @@ -44,11 +45,14 @@ export const MobileMenu = ({ }: MenuProps) => { const [subMenus, setSubMenus] = useState([]); const [open, setOpen] = useState(false); - const mobileContextValue = { - subMenus, - setSubMenus, - setOpen, - }; + const mobileContextValue = useMemo( + () => ({ + subMenus, + setSubMenus, + setOpen, + }), + [subMenus] + ); const { removeSubMenu, removeAllSubMenus } = useMobileSubMenuHelper(mobileContextValue); @@ -95,6 +99,10 @@ export const MobileMenu = ({ }, [onInteractOutside, onPointerDownOutside, removeAllSubMenus, rootOptions] ); + const mobileMenuContextValue = useMemo( + () => ({ subMenus, setSubMenus, setOpen: onOpenChange }), + [onOpenChange, subMenus] + ); useImperativeHandle( ref, @@ -139,9 +147,7 @@ export const MobileMenu = ({ return ( <> {children} - + ({ openConfirmModal: () => {}, closeConfirmModal: () => {}, }); -export const ConfirmModalProvider = ({ children }: PropsWithChildren) => { +export const ConfirmModalProvider = ({ children }: React.PropsWithChildren) => { const [modalProps, setModalProps] = useState({ open: false, }); @@ -200,11 +205,13 @@ export const ConfirmModalProvider = ({ children }: PropsWithChildren) => { }, [modalProps] ); + const confirmModalContextValue = useMemo( + () => ({ openConfirmModal, closeConfirmModal, modalProps }), + [closeConfirmModal, modalProps, openConfirmModal] + ); return ( - + {children} {/* TODO(@catsjuice): multi-instance support(unnecessary for now) */} diff --git a/packages/frontend/component/src/ui/modal/prompt-modal.tsx b/packages/frontend/component/src/ui/modal/prompt-modal.tsx index 9bfe65f687..d5123e4b30 100644 --- a/packages/frontend/component/src/ui/modal/prompt-modal.tsx +++ b/packages/frontend/component/src/ui/modal/prompt-modal.tsx @@ -1,7 +1,13 @@ import { DialogTrigger } from '@radix-ui/react-dialog'; import clsx from 'clsx'; import type { PropsWithChildren } from 'react'; -import { createContext, useCallback, useContext, useState } from 'react'; +import { + createContext, + useCallback, + useContext, + useMemo, + useState, +} from 'react'; import type { ButtonProps } from '../button'; import { Button } from '../button'; @@ -205,15 +211,17 @@ export const PromptModalProvider = ({ children }: PropsWithChildren) => { }, [modalProps] ); + const promptModalContextValue = useMemo( + () => ({ + openPromptModal, + closePromptModal, + modalProps, + }), + [closePromptModal, modalProps, openPromptModal] + ); return ( - + {children} {/* TODO(@catsjuice): multi-instance support(unnecessary for now) */} diff --git a/packages/frontend/core/src/mobile/components/navigation/nodes/tag/dialog.tsx b/packages/frontend/core/src/mobile/components/navigation/nodes/tag/dialog.tsx index 584b987da5..e3ee976128 100644 --- a/packages/frontend/core/src/mobile/components/navigation/nodes/tag/dialog.tsx +++ b/packages/frontend/core/src/mobile/components/navigation/nodes/tag/dialog.tsx @@ -105,11 +105,13 @@ const TagRenameContent = ({ }, [color, onConfirm] ); + const tagColorContextValue = useMemo( + () => ({ colors, color, setColor, show, setShow, enableAnimation }), + [color, colors, enableAnimation, show] + ); return ( - + >; + stack: Array>; }>({ stack: [], }); @@ -162,6 +161,10 @@ export const SwipeDialog = ({ const { stack } = useContext(SwipeDialogContext); const prev = stack[stack.length - 1]?.current; + const swipeDialogContextValue = useMemo( + () => ({ stack: [...stack, dialogRef] }), + [stack] + ); const handleClose = useCallback(() => { onOpenChange?.(false); @@ -222,7 +225,7 @@ export const SwipeDialog = ({ if (!open) return null; return ( - + {createPortal(
diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/index.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/index.tsx index 1819fd6231..72807b22f8 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/index.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/index.tsx @@ -1,4 +1,10 @@ -import { type HTMLAttributes, useCallback, useEffect, useState } from 'react'; +import { + type HTMLAttributes, + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; import { JournalDatePickerContext } from './context'; import { ResizeViewport } from './viewport'; @@ -31,18 +37,21 @@ export const JournalDatePicker = ({ }, [onChange] ); + const width = window.innerWidth; + const journalDatePickerContextValue = useMemo( + () => ({ + selected, + onSelect, + cursor, + setCursor, + width, + withDotDates, + }), + [cursor, onSelect, selected, width, withDotDates] + ); return ( - + ); diff --git a/packages/frontend/core/src/modules/workbench/view/view-root.tsx b/packages/frontend/core/src/modules/workbench/view/view-root.tsx index 9a29528683..b601886edd 100644 --- a/packages/frontend/core/src/modules/workbench/view/view-root.tsx +++ b/packages/frontend/core/src/modules/workbench/view/view-root.tsx @@ -18,6 +18,10 @@ export const ViewRoot = ({ routes: RouteObject[]; }) => { const viewRouter = useMemo(() => createMemoryRouter(routes), [routes]); + const routeContextValue = useMemo( + () => ({ outlet: null, matches: [], isDataRoute: false }), + [] + ); const location = useLiveData(view.location$); @@ -31,13 +35,7 @@ export const ViewRoot = ({ return ( - +