Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Footer/header] feat: new color scheme toggle and cleaner footer design #433

Merged
merged 2 commits into from
Dec 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions apps/web/src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Container } from '@mui/material';
import { Container, Divider } from '@mui/material';
import { Section } from 'ui/core/Section';
import { Nav, NavGroup, NavItem } from 'ui/core/Nav';
import { HorizontalStack } from 'ui/core/HorizontalStack';
Expand All @@ -20,14 +20,16 @@ function FooterLink({ link }: { link: LinkType }) {
layout="icon" // the ones that have no icon will resolve to just text
linkProps={{
color: 'secondary',
variant: 'caption',
}}
sx={{
// Min tap target size
minWidth: 48,
minHeight: 48,
minWidth: 40,
minHeight: 40,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: link.icon ? '1.25em' : undefined,
}}
title={link.title}
/>
Expand All @@ -44,14 +46,14 @@ export function Footer() {
const nonIconFooterLinks = footerLinks?.filter((link) => !link.icon);
const iconFooterLinks = footerLinks?.filter((link) => link.icon);
return (
<Container component={Section} sx={{ padding: 0 }}>
<Container component={Section} sx={{ padding: 0, marginTop: 12 }}>
<footer>
<Divider sx={{ marginBottom: 3 }} />
<Nav
sx={(theme) => ({
flexDirection: 'row',
flexWrap: 'wrap-reverse',
columnGap: 3,
marginTop: 8,
[theme.breakpoints.down('sm')]: {
flexDirection: 'column-reverse',
},
Expand All @@ -72,7 +74,7 @@ export function Footer() {
padding: 0,
margin: 0,
flex: 1,
marginLeft: -1.5,
marginLeft: -2.5,
marginRight: -1.5,
justifyContent: 'space-between',
}}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function Header({ headerRef }: HeaderProps) {
>
<Nav>
<NavGroup>
<NavItem>
<NavItem variant="body2">
<Logo />
</NavItem>
<NavItem>
Expand Down
8 changes: 2 additions & 6 deletions apps/web/src/components/strava/ActivityTypeWithIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { faBicycle } from '@fortawesome/free-solid-svg-icons/faBicycle';
import { faRunning } from '@fortawesome/free-solid-svg-icons/faRunning';
import { Typography } from '@mui/material';
import { HorizontalStack } from 'ui/core/HorizontalStack';
import { FaIcon } from 'ui/icons/FaIcon';
import { Bike, Dumbbell } from 'lucide-react';
import { useData } from 'api/useData';

/**
Expand All @@ -19,9 +17,7 @@ export function ActivityTypeWithIcon() {
const typeText = activity.type.includes('Ride')
? activity.type.replace(/(?<rideType>[A-Z][a-z]+)/g, ' $1')
: 'Run';
const icon = (
<FaIcon icon={activity.type.includes('Ride') ? faBicycle : faRunning} size="1.25em" />
);
const icon = activity.type.includes('Ride') ? <Bike size="1.25em" /> : <Dumbbell size="1.25em" />;

return (
<Typography
Expand Down
23 changes: 23 additions & 0 deletions packages/ui/core/ButtonWithTooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { ButtonProps, TooltipProps } from '@mui/material';
import { Button, Tooltip } from '@mui/material';

/**
* Handles weird layout problems with buttons and tooltips by wrapping it in a div
*/
export function ButtonWithTooltip({
tooltipProps,
buttonProps,
children,
}: {
tooltipProps: Partial<Omit<TooltipProps, 'children'>> & Pick<TooltipProps, 'title'>;
buttonProps: Partial<Omit<ButtonProps, 'children'>>;
children: React.ReactNode;
}) {
return (
<Tooltip {...tooltipProps}>
<div>
<Button {...buttonProps}>{children}</Button>
</div>
</Tooltip>
);
}
84 changes: 84 additions & 0 deletions packages/ui/core/ColorSchemeButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Moon, Sun, Monitor } from 'lucide-react';
import type { Theme } from '@mui/material';
import { useColorScheme } from '../theme/useColorScheme';
import type { ColorSchemeMode } from '../theme/useColorScheme';
import { ButtonWithTooltip } from './ButtonWithTooltip';

type Mode = ColorSchemeMode | 'system';

type ColorSchemeButtonProps = {
/**
* The color scheme mode to switch to on click
*/
mode: Mode;
};

const ICON_SIZE = 13;
const DELAY_MS = 200;

const ICONS: Record<Mode, JSX.Element> = {
light: <Sun size={ICON_SIZE} />,
dark: <Moon size={ICON_SIZE} />,
system: <Monitor size={ICON_SIZE} />,
} as const;

function getColor(mode: Mode, theme: Theme) {
switch (mode) {
case 'light':
return theme.vars.palette.warning.main;
case 'dark':
return theme.vars.palette.secondary.light;
case 'system':
return theme.vars.palette.primary.dark;
}
}

/**
* Creates an icon that is clickable to update to a preferred color scheme
* if we have one that's set already. Otherwise, renders a disabled icon.
*/
export function ColorSchemeButton({ mode }: ColorSchemeButtonProps) {
const { updatePreferredMode } = useColorScheme();
const tooltip = (() => {
switch (mode) {
case 'light':
return 'Use light color mode';
case 'dark':
return 'Use dark color mode';
case 'system':
return `Use system color mode`;
}
})();

return (
<ButtonWithTooltip
buttonProps={{
size: 'small',
'aria-label': tooltip,
onClick: () => {
updatePreferredMode(mode === 'system' ? null : mode);
},
sx: (theme) => ({
paddingY: 0.75,
paddingX: 0.25,
'& svg': {
transition: theme.transitions.create('transform'),
transformOrigin: 'center',
},
'&:hover svg': {
color: getColor(mode, theme),
transform: mode === 'system' ? undefined : 'rotate(45deg)',
},
}),
}}
tooltipProps={{
enterDelay: DELAY_MS,
enterNextDelay: DELAY_MS,
enterTouchDelay: DELAY_MS,
title: tooltip,
}}
>
{ICONS[mode]}
</ButtonWithTooltip>
);
}
84 changes: 0 additions & 84 deletions packages/ui/core/ColorSchemeIcon.tsx

This file was deleted.

77 changes: 7 additions & 70 deletions packages/ui/core/ColorSchemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,81 +1,18 @@
import { Stack, Switch, switchClasses } from '@mui/material';
import { useColorScheme } from '../theme/useColorScheme';
import { ColorSchemeIcon } from './ColorSchemeIcon';

const HEIGHT_PX = 24;
const WIDTH_PX = 48;
const PADDING = 0.5;
const PADDING_PX = PADDING * 8;
const TRACK_SIZE_PX = HEIGHT_PX - 2 * PADDING_PX;
import { ButtonGroup, Stack } from '@mui/material';
import { ColorSchemeButton } from './ColorSchemeButton';

/**
* Provides the ability to toggle the page's color scheme between
* system, light, and dark. Prerendered, `mode` is `light`.
*/
export function ColorSchemeToggle() {
const { colorScheme, updatePreferredMode } = useColorScheme();
const setInvertedScheme = () => {
updatePreferredMode(colorScheme.mode === 'dark' ? 'light' : 'dark');
};

return (
<Stack sx={{ flexDirection: 'row', alignItems: 'center' }}>
<ColorSchemeIcon mode="light" />
<Switch
aria-hidden
aria-label="Change color scheme mode"
checked={colorScheme.isInitialized ? colorScheme.mode === 'dark' : false}
onChange={setInvertedScheme}
sx={(theme) => ({
// Overrides our "animations off" for color scheme changes since we do always want the switch to animate
'& *': {
transition: `${theme.transitions.create('all', {
duration: theme.transitions.duration.complex,
})} !important`,
},
height: HEIGHT_PX,
width: WIDTH_PX,
margin: 0,
padding: 0,

[`&& .${switchClasses.switchBase}`]: {
cursor: colorScheme.isInitialized ? 'pointer' : 'not-allowed',
margin: PADDING,
padding: 0,

[`&.${switchClasses.checked}`]: {
transform: `translateX(${WIDTH_PX - TRACK_SIZE_PX - 2 * PADDING_PX}px)`,
[`&& + .${switchClasses.track}`]: {
opacity: 1,
backgroundColor: colorScheme.isCustomized
? theme.vars.palette.active.main
: theme.vars.palette.card.border,
},
},
},
[`&& .${switchClasses.input}`]: {
width: WIDTH_PX * 2 + TRACK_SIZE_PX,
height: HEIGHT_PX,
left: -WIDTH_PX - TRACK_SIZE_PX / 2,
top: -PADDING_PX,
},
[`& .${switchClasses.thumb}`]: {
backgroundColor: theme.vars.palette.common.white,
width: TRACK_SIZE_PX,
height: TRACK_SIZE_PX,
},
[`&&& .${switchClasses.track}`]: {
opacity: 1,
backgroundColor: colorScheme.isCustomized
? theme.vars.palette.active.main
: theme.vars.palette.card.border,
borderRadius: HEIGHT_PX / 2,
transition: theme.transitions.create('background-color'),
},
})}
tabIndex={-1}
/>
<ColorSchemeIcon mode="dark" />
<ButtonGroup aria-label="outlined primary button group" size="small" variant="outlined">
<ColorSchemeButton mode="light" />
<ColorSchemeButton mode="dark" />
<ColorSchemeButton mode="system" />
</ButtonGroup>
</Stack>
);
}
2 changes: 1 addition & 1 deletion packages/ui/core/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function NavItem({ sx, children, ...props }: TypographyProps) {
return (
<Typography
component="li"
variant="body2"
variant="caption"
{...props}
sx={mixinSx(
(theme) => ({
Expand Down
8 changes: 4 additions & 4 deletions packages/ui/core/Section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import { mixinSx } from '../helpers/mixinSx';

export const sectionSx: SxProps = (theme) => ({
[theme.breakpoints.up('sm')]: {
marginBottom: 5.3125,
marginBottom: 3,
},
[theme.breakpoints.up('md')]: {
marginBottom: 6.375,
marginBottom: 3.6,
},
[theme.breakpoints.up('lg')]: {
marginBottom: 7.4375,
marginBottom: 4.32,
},
[theme.breakpoints.up('xl')]: {
marginBottom: 8.5,
marginBottom: 5.184,
},
});

Expand Down
Loading