Skip to content
Snippets Groups Projects
Unverified Commit 9bccacea authored by Jack's avatar Jack Committed by GitHub
Browse files

Month picker rework (#1235)


Co-authored-by: default avatarJed Fox <git@jedfox.com>
parent b06d87d2
No related branches found
No related tags found
No related merge requests found
import { useState } from 'react';
import * as monthUtils from 'loot-core/src/shared/months';
import useResizeObserver from '../../hooks/useResizeObserver';
import { styles, colors } from '../../style';
import { View } from '../common';
export const MonthPicker = ({
startMonth,
numDisplayed,
monthBounds,
style,
onSelect,
}) => {
const [hoverId, setHoverId] = useState(null);
const [targetMonthCount, setTargetMonthCount] = useState(12);
const currentMonth = monthUtils.currentMonth();
const firstSelectedMonth = startMonth;
const lastSelectedMonth = monthUtils.addMonths(
firstSelectedMonth,
numDisplayed - 1,
);
const range = monthUtils.rangeInclusive(
monthUtils.subMonths(
firstSelectedMonth,
targetMonthCount / 2 - numDisplayed / 2,
),
monthUtils.addMonths(
lastSelectedMonth,
targetMonthCount / 2 - numDisplayed / 2,
),
);
const firstSelectedIndex =
Math.floor(range.length / 2) - Math.floor(numDisplayed / 2);
const lastSelectedIndex = firstSelectedIndex + numDisplayed - 1;
const [size, setSize] = useState('small');
const containerRef = useResizeObserver(rect => {
setSize(rect.width <= 400 ? 'small' : 'big');
setTargetMonthCount(
Math.min(Math.max(Math.floor(rect.width / 50), 12), 24),
);
});
let yearHeadersShown = [];
return (
<View
style={[
{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
style,
]}
>
<View
innerRef={containerRef}
style={{
flexDirection: 'row',
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}}
>
{range.map((month, idx) => {
const monthName = monthUtils.format(month, 'MMM');
const selected =
idx >= firstSelectedIndex && idx <= lastSelectedIndex;
const lastHoverId = hoverId + numDisplayed - 1;
const hovered =
hoverId === null ? false : idx >= hoverId && idx <= lastHoverId;
const current = currentMonth === month;
const year = monthUtils.getYear(month);
let showYearHeader = false;
if (!yearHeadersShown.includes(year)) {
yearHeadersShown.push(year);
showYearHeader = true;
}
const isMonthBudgeted =
month >= monthBounds.start && month <= monthBounds.end;
return (
<View
key={month}
style={[
{
padding: '3px 3px',
width: size === 'big' ? '35px' : '20px',
textAlign: 'center',
userSelect: 'none',
cursor: 'default',
borderRadius: 2,
border: 'none',
},
!isMonthBudgeted && {
textDecoration: 'line-through',
color: colors.n7,
},
styles.smallText,
selected && {
backgroundColor: colors.p6,
color: 'white',
},
(hovered || selected) && {
borderRadius: 0,
cursor: 'pointer',
},
hovered &&
!selected && {
backgroundColor: 'rgba(100, 100, 100, .15)',
},
hovered &&
selected && {
backgroundColor: colors.p7,
},
(idx === firstSelectedIndex ||
(idx === hoverId && !selected)) && {
borderTopLeftRadius: 2,
borderBottomLeftRadius: 2,
},
(idx === lastSelectedIndex ||
(idx === lastHoverId && !selected)) && {
borderTopRightRadius: 2,
borderBottomRightRadius: 2,
},
current && { fontWeight: 'bold' },
]}
onClick={() => onSelect(month)}
onMouseEnter={() => setHoverId(idx)}
onMouseLeave={() => setHoverId(null)}
>
{size === 'small' ? monthName[0] : monthName}
{showYearHeader && (
<View
style={[
{
position: 'absolute',
top: -14,
left: 0,
fontSize: 10,
fontWeight: 'bold',
color: isMonthBudgeted ? '#272630' : colors.n7,
},
]}
>
{year}
</View>
)}
</View>
);
})}
</View>
</View>
);
};
......@@ -13,10 +13,7 @@ import { bindActionCreators } from 'redux';
import * as actions from 'loot-core/src/client/actions';
import * as monthUtils from 'loot-core/src/shared/months';
import useResizeObserver from '../../hooks/useResizeObserver';
import ExpandArrow from '../../icons/v0/ExpandArrow';
import ArrowThinLeft from '../../icons/v1/ArrowThinLeft';
import ArrowThinRight from '../../icons/v1/ArrowThinRight';
import CheveronDown from '../../icons/v1/CheveronDown';
import DotsHorizontalTriple from '../../icons/v1/DotsHorizontalTriple';
import { styles, colors } from '../../style';
......@@ -40,6 +37,7 @@ import { Row, InputCell, ROW_HEIGHT } from '../table';
import BudgetSummaries from './BudgetSummaries';
import { INCOME_HEADER_HEIGHT, MONTH_BOX_SHADOW } from './constants';
import { MonthPicker } from './MonthPicker';
import { MonthsProvider, MonthsContext } from './MonthsContext';
import { separateGroups, findSortDown, findSortUp } from './util';
......@@ -1388,150 +1386,3 @@ export const BudgetPageHeader = memo(
);
},
);
function getRangeForYear(year) {
return monthUtils.rangeInclusive(
monthUtils.getYearStart(year),
monthUtils.getYearEnd(year),
);
}
function getMonth(year, idx) {
return monthUtils.addMonths(year, idx);
}
function getCurrentMonthName(startMonth, currentMonth) {
return monthUtils.getYear(startMonth) === monthUtils.getYear(currentMonth)
? monthUtils.format(currentMonth, 'MMM')
: null;
}
const MonthPicker = ({
startMonth,
numDisplayed,
monthBounds,
style,
onSelect,
}) => {
const currentMonth = monthUtils.currentMonth();
const range = getRangeForYear(currentMonth);
const monthNames = range.map(month => {
return monthUtils.format(month, 'MMM');
});
const currentMonthName = getCurrentMonthName(startMonth, currentMonth);
const year = monthUtils.getYear(startMonth);
const selectedIndex = monthUtils.getMonthIndex(startMonth);
const [size, setSize] = useState('small');
const containerRef = useResizeObserver(rect => {
setSize(rect.width <= 320 ? 'small' : rect.width <= 400 ? 'medium' : 'big');
});
return (
<View
style={[
{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
style,
]}
>
<View
style={{
padding: '3px 0px',
margin: '3px 0',
fontWeight: 'bold',
fontSize: 14,
flex: '0 0 40px',
}}
>
{monthUtils.format(year, 'yyyy')}
</View>
<View
innerRef={containerRef}
style={{
flexDirection: 'row',
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}}
>
{monthNames.map((monthName, idx) => {
const lastSelectedIndex = selectedIndex + numDisplayed;
const selected = idx >= selectedIndex && idx < lastSelectedIndex;
const current = monthName === currentMonthName;
const month = getMonth(year, idx);
const isMonthBudgeted =
month >= monthBounds.start && month <= monthBounds.end;
return (
<View
key={monthName}
style={[
{
marginRight: 1,
padding: size === 'big' ? '3px 5px' : '3px 3px',
textAlign: 'center',
cursor: 'default',
borderRadius: 2,
':hover': isMonthBudgeted && {
backgroundColor: colors.p6,
color: 'white',
},
},
!isMonthBudgeted && { color: colors.n7 },
styles.smallText,
selected && {
backgroundColor: colors.p6,
color: 'white',
borderRadius: 0,
},
idx === selectedIndex && {
borderTopLeftRadius: 2,
borderBottomLeftRadius: 2,
},
idx === lastSelectedIndex - 1 && {
borderTopRightRadius: 2,
borderBottomRightRadius: 2,
},
idx >= selectedIndex &&
idx < lastSelectedIndex - 1 && {
marginRight: 0,
borderRight: 'solid 1px',
borderColor: colors.p6,
},
current && { textDecoration: 'underline' },
]}
onClick={() => onSelect(month)}
>
{size === 'small' ? monthName[0] : monthName}
</View>
);
})}
</View>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
flex: '0 0 50px',
justifyContent: 'flex-end',
}}
>
<Button
onClick={() => onSelect(monthUtils.subMonths(startMonth, 1))}
bare
>
<ArrowThinLeft width={12} height={12} />
</Button>
<Button
onClick={() => onSelect(monthUtils.addMonths(startMonth, 1))}
bare
>
<ArrowThinRight width={12} height={12} />
</Button>
</View>
</View>
);
};
......@@ -343,7 +343,7 @@ export function BudgetSummary({
fontWeight: 500,
textDecorationSkip: 'ink',
},
currentMonth === month && { textDecoration: 'underline' },
currentMonth === month && { fontWeight: 'bold' },
])}
>
{monthUtils.format(month, 'MMMM')}
......
---
category: Enhancements
authors: [Miodec]
---
Reworked the budget month picker
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