Skip to content
Snippets Groups Projects
Unverified Commit f20e9c6b authored by Joel Jeremy Marquez's avatar Joel Jeremy Marquez Committed by GitHub
Browse files

[Mobile] Schedule name and date in mobile scheduled transaction modal (#2664)

* [Mobile] Add schedule name and date to scheduled transaction modal

* useRef to prevent rerenders

* Release notes

* Fix typecheck error

* Fix lint errors

* Update schedule font

* Heavier font weight
parent f73f6c7f
No related branches found
No related tags found
No related merge requests found
...@@ -66,7 +66,7 @@ export function NotesButton({ ...@@ -66,7 +66,7 @@ export function NotesButton({
setIsOpen(true); setIsOpen(true);
}} }}
> >
<SvgCustomNotesPaper style={{ width, height }} /> <SvgCustomNotesPaper style={{ width, height, flexShrink: 0 }} />
</Button> </Button>
</View> </View>
......
...@@ -12,15 +12,20 @@ import React, { ...@@ -12,15 +12,20 @@ import React, {
import { useHotkeysContext } from 'react-hotkeys-hook'; import { useHotkeysContext } from 'react-hotkeys-hook';
import ReactModal from 'react-modal'; import ReactModal from 'react-modal';
import { useHover } from 'usehooks-ts';
import { useMergedRefs } from '../../hooks/useMergedRefs';
import { AnimatedLoading } from '../../icons/AnimatedLoading'; import { AnimatedLoading } from '../../icons/AnimatedLoading';
import { SvgLogo } from '../../icons/logo'; import { SvgLogo } from '../../icons/logo';
import { SvgDelete } from '../../icons/v0'; import { SvgDelete } from '../../icons/v0';
import { SvgClose } from '../../icons/v1';
import { type CSSProperties, styles, theme } from '../../style'; import { type CSSProperties, styles, theme } from '../../style';
import { tokens } from '../../tokens'; import { tokens } from '../../tokens';
import { Button } from './Button'; import { Button } from './Button';
import { Input } from './Input'; import { Input } from './Input';
import { Text } from './Text'; import { Text } from './Text';
import { Tooltip } from './Tooltip';
import { View } from './View'; import { View } from './View';
export type ModalProps = { export type ModalProps = {
...@@ -372,14 +377,42 @@ export function ModalTitle({ ...@@ -372,14 +377,42 @@ export function ModalTitle({
shrinkOnOverflow = false, shrinkOnOverflow = false,
}: ModalTitleProps) { }: ModalTitleProps) {
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const style = getStyle?.(isEditing); const tooltipTriggerRef = useRef();
const _onEdit = () => { // Dynamic font size to avoid ellipsis.
if (!isEditable) { const textRef = useRef<HTMLSpanElement>();
return; const [textOverflowed, setTextOverflowed] = useState(false);
const [textFontSize, setTextFontSize] = useState(25);
useEffect(() => {
const containerWidth = textRef.current.offsetWidth;
const textWidth = textRef.current.scrollWidth;
if (textWidth > containerWidth) {
setTextOverflowed(true);
} else {
setTextOverflowed(false);
} }
}, [title]);
setIsEditing(true); useEffect(() => {
if (textOverflowed && shrinkOnOverflow) {
const containerWidth = textRef.current.offsetWidth;
const textWidth = textRef.current.scrollWidth;
const newFontSize = Math.floor(
(containerWidth / textWidth) * textFontSize,
);
setTextFontSize(newFontSize);
}
}, [title, textFontSize, shrinkOnOverflow, textOverflowed]);
const mergedTextRef = useMergedRefs(textRef, tooltipTriggerRef);
const isHovered = useHover(tooltipTriggerRef);
const _onEdit = () => {
if (isEditable) {
setIsEditing(true);
}
}; };
const _onTitleUpdate = newTitle => { const _onTitleUpdate = newTitle => {
...@@ -398,22 +431,7 @@ export function ModalTitle({ ...@@ -398,22 +431,7 @@ export function ModalTitle({
} }
}, [isEditing]); }, [isEditing]);
// Dynamic font size to avoid ellipsis. const style = getStyle?.(isEditing);
const textRef = useRef<HTMLSpanElement>();
const [textFontSize, setTextFontSize] = useState(25);
useEffect(() => {
if (shrinkOnOverflow) {
const containerWidth = textRef.current.offsetWidth;
const textWidth = textRef.current.scrollWidth;
if (textWidth > containerWidth) {
const newFontSize = Math.floor(
(containerWidth / textWidth) * textFontSize,
);
setTextFontSize(newFontSize);
}
}
}, [textFontSize, shrinkOnOverflow]);
return isEditing ? ( return isEditing ? (
<Input <Input
...@@ -435,22 +453,42 @@ export function ModalTitle({ ...@@ -435,22 +453,42 @@ export function ModalTitle({
}} }}
/> />
) : ( ) : (
<Text <Tooltip
innerRef={textRef} content={
style={{ <View
fontSize: textFontSize, style={{
fontWeight: 700, flexDirection: 'row',
textAlign: 'center', alignItems: 'center',
whiteSpace: 'nowrap', gap: 10,
overflow: 'hidden', maxWidth: '90vw',
textOverflow: 'ellipsis', }}
...(isEditable && styles.underlinedText), >
...style, <SvgClose style={{ width: 10, height: 10, flexShrink: 0 }} />
}} <Text style={styles.mediumText}>{title}</Text>
onClick={_onEdit} </View>
}
placement="top"
triggerRef={tooltipTriggerRef}
isOpen={textOverflowed && isHovered}
offset={10}
> >
{title} <Text
</Text> innerRef={mergedTextRef}
style={{
fontSize: textFontSize,
fontWeight: 700,
textAlign: 'center',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
...(isEditable && styles.underlinedText),
...style,
}}
onClick={_onEdit}
>
{title}
</Text>
</Tooltip>
); );
} }
......
...@@ -25,44 +25,39 @@ export const Tooltip = ({ ...@@ -25,44 +25,39 @@ export const Tooltip = ({
...props ...props
}: TooltipProps) => { }: TooltipProps) => {
const triggerRef = useRef(null); const triggerRef = useRef(null);
const [hover, setHover] = useState(false); const [isHovered, setIsHover] = useState(false);
const [delayHandler, setDelayHandler] = useState<ReturnType< const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
typeof setTimeout
> | null>(null);
const handleMouseEnter = useCallback(() => { const handlePointerEnter = useCallback(() => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
setHover(true); setIsHover(true);
}, triggerProps.delay ?? 300); }, triggerProps.delay ?? 300);
setDelayHandler(timeout); hoverTimeoutRef.current = timeout;
return () => {
clearTimeout(timeout);
};
}, [triggerProps.delay]); }, [triggerProps.delay]);
const handleMouseLeave = useCallback(() => { const handlePointerLeave = useCallback(() => {
if (delayHandler) { if (hoverTimeoutRef.current) {
clearTimeout(delayHandler); clearTimeout(hoverTimeoutRef.current);
} }
setHover(false); setIsHover(false);
}, [delayHandler]); }, []);
// Force closing the tooltip whenever the disablement state changes // Force closing the tooltip whenever the disablement state changes
useEffect(() => { useEffect(() => {
setHover(false); setIsHover(false);
}, [triggerProps.isDisabled]); }, [triggerProps.isDisabled]);
return ( return (
<View <View
ref={triggerRef} ref={triggerRef}
onMouseEnter={handleMouseEnter} onPointerEnter={handlePointerEnter}
onMouseLeave={handleMouseLeave} onPointerLeave={handlePointerLeave}
> >
<TooltipTrigger <TooltipTrigger
isOpen={hover && !triggerProps.isDisabled} isOpen={isHovered && !triggerProps.isDisabled}
{...triggerProps} {...triggerProps}
> >
{children} {children}
......
import React, { type ComponentPropsWithoutRef } from 'react'; import React, { useCallback, type ComponentPropsWithoutRef } from 'react';
import { useSchedules } from 'loot-core/client/data-hooks/schedules';
import { format } from 'loot-core/shared/months';
import { type Query } from 'loot-core/shared/query';
import { type CSSProperties, theme, styles } from '../../style'; import { type CSSProperties, theme, styles } from '../../style';
import { Menu } from '../common/Menu'; import { Menu } from '../common/Menu';
import { Modal } from '../common/Modal'; import { Modal } from '../common/Modal';
import { Text } from '../common/Text';
import { View } from '../common/View';
import { type CommonModalProps } from '../Modals'; import { type CommonModalProps } from '../Modals';
type ScheduledTransactionMenuModalProps = ScheduledTransactionMenuProps & { type ScheduledTransactionMenuModalProps = ScheduledTransactionMenuProps & {
...@@ -21,9 +27,38 @@ export function ScheduledTransactionMenuModal({ ...@@ -21,9 +27,38 @@ export function ScheduledTransactionMenuModal({
borderRadius: 0, borderRadius: 0,
borderTop: `1px solid ${theme.pillBorder}`, borderTop: `1px solid ${theme.pillBorder}`,
}; };
const scheduleId = transactionId?.split('/')?.[1];
const scheduleData = useSchedules({
transform: useCallback(
(q: Query) => q.filter({ id: scheduleId }),
[scheduleId],
),
});
const schedule = scheduleData?.schedules?.[0];
if (!schedule) {
return;
}
return ( return (
<Modal showHeader focusAfterClose={false} {...modalProps}> <Modal
title={schedule.name}
showHeader
focusAfterClose={false}
{...modalProps}
>
<View
style={{
justifyContent: 'center',
alignItems: 'center',
marginBottom: 20,
}}
>
<Text style={{ fontSize: 17, fontWeight: 400 }}>Scheduled date</Text>
<Text style={{ fontSize: 17, fontWeight: 700 }}>
{format(schedule.next_date, 'MMMM dd, yyyy')}
</Text>
</View>
<ScheduledTransactionMenu <ScheduledTransactionMenu
transactionId={transactionId} transactionId={transactionId}
onPost={onPost} onPost={onPost}
......
---
category: Enhancements
authors: [joel-jeremy]
---
Add schedule name and date to mobile scheduled transaction modal.
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment