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({
setIsOpen(true);
}}
>
<SvgCustomNotesPaper style={{ width, height }} />
<SvgCustomNotesPaper style={{ width, height, flexShrink: 0 }} />
</Button>
</View>
......
......@@ -12,15 +12,20 @@ import React, {
import { useHotkeysContext } from 'react-hotkeys-hook';
import ReactModal from 'react-modal';
import { useHover } from 'usehooks-ts';
import { useMergedRefs } from '../../hooks/useMergedRefs';
import { AnimatedLoading } from '../../icons/AnimatedLoading';
import { SvgLogo } from '../../icons/logo';
import { SvgDelete } from '../../icons/v0';
import { SvgClose } from '../../icons/v1';
import { type CSSProperties, styles, theme } from '../../style';
import { tokens } from '../../tokens';
import { Button } from './Button';
import { Input } from './Input';
import { Text } from './Text';
import { Tooltip } from './Tooltip';
import { View } from './View';
export type ModalProps = {
......@@ -372,14 +377,42 @@ export function ModalTitle({
shrinkOnOverflow = false,
}: ModalTitleProps) {
const [isEditing, setIsEditing] = useState(false);
const style = getStyle?.(isEditing);
const tooltipTriggerRef = useRef();
const _onEdit = () => {
if (!isEditable) {
return;
// Dynamic font size to avoid ellipsis.
const textRef = useRef<HTMLSpanElement>();
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 => {
......@@ -398,22 +431,7 @@ export function ModalTitle({
}
}, [isEditing]);
// Dynamic font size to avoid ellipsis.
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]);
const style = getStyle?.(isEditing);
return isEditing ? (
<Input
......@@ -435,22 +453,42 @@ export function ModalTitle({
}}
/>
) : (
<Text
innerRef={textRef}
style={{
fontSize: textFontSize,
fontWeight: 700,
textAlign: 'center',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
...(isEditable && styles.underlinedText),
...style,
}}
onClick={_onEdit}
<Tooltip
content={
<View
style={{
flexDirection: 'row',
alignItems: 'center',
gap: 10,
maxWidth: '90vw',
}}
>
<SvgClose style={{ width: 10, height: 10, flexShrink: 0 }} />
<Text style={styles.mediumText}>{title}</Text>
</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 = ({
...props
}: TooltipProps) => {
const triggerRef = useRef(null);
const [hover, setHover] = useState(false);
const [isHovered, setIsHover] = useState(false);
const [delayHandler, setDelayHandler] = useState<ReturnType<
typeof setTimeout
> | null>(null);
const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
const handleMouseEnter = useCallback(() => {
const handlePointerEnter = useCallback(() => {
const timeout = setTimeout(() => {
setHover(true);
setIsHover(true);
}, triggerProps.delay ?? 300);
setDelayHandler(timeout);
return () => {
clearTimeout(timeout);
};
hoverTimeoutRef.current = timeout;
}, [triggerProps.delay]);
const handleMouseLeave = useCallback(() => {
if (delayHandler) {
clearTimeout(delayHandler);
const handlePointerLeave = useCallback(() => {
if (hoverTimeoutRef.current) {
clearTimeout(hoverTimeoutRef.current);
}
setHover(false);
}, [delayHandler]);
setIsHover(false);
}, []);
// Force closing the tooltip whenever the disablement state changes
useEffect(() => {
setHover(false);
setIsHover(false);
}, [triggerProps.isDisabled]);
return (
<View
ref={triggerRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onPointerEnter={handlePointerEnter}
onPointerLeave={handlePointerLeave}
>
<TooltipTrigger
isOpen={hover && !triggerProps.isDisabled}
isOpen={isHovered && !triggerProps.isDisabled}
{...triggerProps}
>
{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 { Menu } from '../common/Menu';
import { Modal } from '../common/Modal';
import { Text } from '../common/Text';
import { View } from '../common/View';
import { type CommonModalProps } from '../Modals';
type ScheduledTransactionMenuModalProps = ScheduledTransactionMenuProps & {
......@@ -21,9 +27,38 @@ export function ScheduledTransactionMenuModal({
borderRadius: 0,
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 (
<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
transactionId={transactionId}
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