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

feat: Custom zoom buttons #6974

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions web/cypress/e2e/map.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe('Map', () => {
// test dark mode
// cy.get('[data-test-id=dark-mode-button]').click().click();

// eslint-disable-next-line cypress/require-data-selectors
cy.get('.maplibregl-ctrl-zoom-in.maplibregl-ctrl-zoom-in').click();
cy.get('[data-test-id=zoom-in]').click();
cy.get('[data-test-id=zoom-out]').click();
});
});
6 changes: 5 additions & 1 deletion web/src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ interface ButtonProps {
disabled?: boolean;
size?: SizeOptions;
shouldShrink?: boolean;
type?: 'primary' | 'secondary' | 'tertiary' | 'link';
type?: 'primary' | 'secondary' | 'tertiary' | 'link' | 'transparent';
madsnedergaard marked this conversation as resolved.
Show resolved Hide resolved
href?: string;
backgroundClasses?: string;
foregroundClasses?: string;
asDiv?: boolean;
onClick?: () => void;
ariaLabel?: string;
}

export function Button({
Expand Down Expand Up @@ -103,6 +104,9 @@ function getBackground(type: string, disabled: boolean | undefined) {
case 'secondary': {
return 'border dark:border-gray-700 border-neutral-200 bg-white dark:bg-gray-900';
}
case 'transparent': {
return 'bg-white/80 dark:bg-gray-800/80 border border-neutral-200 dark:border-gray-700 backdrop-blur';
}
default: {
return 'bg-inherit';
}
Expand Down
33 changes: 33 additions & 0 deletions web/src/features/map-controls/ColorblindToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useAtom } from 'jotai';
import { useTranslation } from 'react-i18next';
import { HiOutlineEyeOff } from 'react-icons/hi';
import trackEvent from 'utils/analytics';
import { colorblindModeAtom } from 'utils/state/atoms';

import MapButton from './MapButton';

export default function ColorblindToggle() {
const { t } = useTranslation();
const [isColorblindModeEnabled, setIsColorblindModeEnabled] =
useAtom(colorblindModeAtom);

const handleColorblindModeToggle = () => {
setIsColorblindModeEnabled(!isColorblindModeEnabled);
trackEvent('Colorblind Mode Toggled');
};

return (
<MapButton
icon={
<HiOutlineEyeOff
size={20}
className={`${isColorblindModeEnabled ? '' : 'opacity-50'}`}
/>
}
dataTestId="colorblind-layer-button"
tooltipText={t('legends.colorblindmode')}
onClick={handleColorblindModeToggle}
ariaLabel={t('aria.label.colorBlindMode')}
/>
);
}
25 changes: 9 additions & 16 deletions web/src/features/map-controls/MapButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as Toggle from '@radix-ui/react-toggle';
import { twMerge } from 'tailwind-merge';
VIKTORVAV99 marked this conversation as resolved.
Show resolved Hide resolved
import { Button } from 'components/Button';

import TooltipWrapper from '../../components/tooltips/TooltipWrapper';

Expand All @@ -9,35 +8,29 @@ interface MapButtonProperties {
tooltipText?: string;
className?: string;
dataTestId?: string;
asToggle?: boolean;
ariaLabel?: string;
backgroundClasses?: string;
}

export default function MapButton({
icon,
VIKTORVAV99 marked this conversation as resolved.
Show resolved Hide resolved
tooltipText,
className,
dataTestId,
onClick,
asToggle,
ariaLabel,
backgroundClasses,
}: MapButtonProperties) {
const Component = asToggle ? Toggle.Root : 'div';
return (
<TooltipWrapper tooltipContent={tooltipText}>
<Component
<Button
size="md"
type="transparent"
icon={icon}
onClick={onClick}
className={twMerge(
`flex h-8 w-8 items-center justify-center rounded bg-white/80 text-left shadow-lg backdrop-blur-sm transition hover:bg-white dark:bg-gray-800/80 dark:hover:bg-gray-900/90`,
className,
asToggle && 'pointer-events-auto'
)}
aria-label={ariaLabel}
data-test-id={dataTestId}
role="button"
>
<div>{icon}</div>
</Component>
backgroundClasses={backgroundClasses}
/>
</TooltipWrapper>
);
}
35 changes: 7 additions & 28 deletions web/src/features/map-controls/MapControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import { useAtom, useSetAtom } from 'jotai';
import { useTransition } from 'react';
import { useTranslation } from 'react-i18next';
import { FiWind } from 'react-icons/fi';
import { HiOutlineEyeOff, HiOutlineSun } from 'react-icons/hi';
import { HiOutlineSun } from 'react-icons/hi';
import { HiCog6Tooth, HiOutlineInformationCircle } from 'react-icons/hi2';
import { MoonLoader } from 'react-spinners';
import trackEvent from 'utils/analytics';
import { ThemeOptions, TimeAverages, ToggleOptions } from 'utils/constants';
import {
colorblindModeAtom,
selectedDatetimeIndexAtom,
solarLayerEnabledAtom,
solarLayerLoadingAtom,
Expand All @@ -20,11 +19,13 @@ import {
windLayerLoadingAtom,
} from 'utils/state/atoms';

import ColorblindToggle from './ColorblindToggle';
import ConsumptionProductionToggle from './ConsumptionProductionToggle';
import { LanguageSelector } from './LanguageSelector';
import MapButton from './MapButton';
import SpatialAggregatesToggle from './SpatialAggregatesToggle';
import ThemeSelector from './ThemeSelector';
import ZoomControls from './ZoomControls';

function MobileMapControls() {
const setIsInfoModalOpen = useSetAtom(isInfoModalOpenAtom);
Expand Down Expand Up @@ -114,55 +115,33 @@ function WeatherButton({ type }: { type: 'wind' | 'solar' }) {
className={`${isLoadingLayer ? 'cursor-default' : 'cursor-pointer'}`}
onClick={isLoadingLayer ? () => {} : onToggle}
ariaLabel={type == 'wind' ? t('aria.label.windLayer') : t('aria.label.solarLayer')}
asToggle
/>
);
}

function DesktopMapControls() {
const { t } = useTranslation();
const [timeAverage] = useAtom(timeAverageAtom);
const [selectedDatetime] = useAtom(selectedDatetimeIndexAtom);
const [isColorblindModeEnabled, setIsColorblindModeEnabled] =
useAtom(colorblindModeAtom);

// We are currently only supporting and fetching weather data for the latest hourly value
const areWeatherLayersAllowed =
selectedDatetime.index === 24 && timeAverage === TimeAverages.HOURLY;

const handleColorblindModeToggle = () => {
setIsColorblindModeEnabled(!isColorblindModeEnabled);
trackEvent('Colorblind Mode Toggled');
};

return (
<div className="pointer-events-none absolute right-3 top-2 z-30 hidden flex-col items-end md:flex">
<div className="pointer-events-auto mb-16 flex flex-col items-end space-y-2">
<div className="pointer-events-auto flex flex-col items-end gap-2">
<ConsumptionProductionToggle />
<SpatialAggregatesToggle />
</div>
<div className="mt-5 space-y-2">
<ZoomControls />
<LanguageSelector />
<MapButton
icon={
<HiOutlineEyeOff
size={20}
className={`${isColorblindModeEnabled ? '' : 'opacity-50'}`}
/>
}
dataTestId="colorblind-layer-button"
tooltipText={t('legends.colorblindmode')}
onClick={handleColorblindModeToggle}
asToggle
ariaLabel={t('aria.label.colorBlindMode')}
/>
<ColorblindToggle />
<ThemeSelector />
{areWeatherLayersAllowed && (
<>
<WeatherButton type="wind" />
<WeatherButton type="solar" />
</>
)}
<ThemeSelector />
</div>
</div>
);
Expand Down
40 changes: 23 additions & 17 deletions web/src/features/map-controls/ZoomControls.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import { ReactElement } from 'react';
import { NavigationControl } from 'react-map-gl/maplibre';
import { useTranslation } from 'react-i18next';
import { FaMinus, FaPlus } from 'react-icons/fa6';
import { useMap } from 'react-map-gl/maplibre';

import MapButton from './MapButton';

export default function ZoomControls(): ReactElement {
const { map } = useMap();
const { t } = useTranslation();
return (
<NavigationControl
style={{
marginRight: 12,
marginTop: 98,
width: '33px',
boxShadow: '0px 1px 1px rgb(0 0 0 / 0.1)',
border: 0,
color: 'white',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
showCompass={false}
//TODO: Find a way to use a __('tooltips.zoomIn') as aria-label here
//TODO: Find a way to use a __('tooltips.zoomOut') as aria-label here
/>
<div className="flex flex-col">
<MapButton
icon={<FaPlus size={20} />}
onClick={() => map?.zoomIn()}
ariaLabel={t('tooltips.zoomIn')}
dataTestId="zoom-in"
backgroundClasses="rounded-none rounded-t-full"
VIKTORVAV99 marked this conversation as resolved.
Show resolved Hide resolved
/>
<MapButton
icon={<FaMinus size={20} />}
onClick={() => map?.zoomOut()}
ariaLabel={t('tooltips.zoomOut')}
dataTestId="zoom-out"
backgroundClasses="rounded-none rounded-b-full"
/>
</div>
);
}
3 changes: 1 addition & 2 deletions web/src/features/map/Map.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import useGetState from 'api/getState';
import ExchangeLayer from 'features/exchanges/ExchangeLayer';
import ZoomControls from 'features/map-controls/ZoomControls';
import { leftPanelOpenAtom } from 'features/panels/panelAtoms';
import SolarLayer from 'features/weather-layers/solar/SolarLayer';
import WindLayer from 'features/weather-layers/wind-layer/WindLayer';
Expand Down Expand Up @@ -344,6 +343,7 @@ export default function MapPage({ onMapLoad }: MapPageProps): ReactElement {

return (
<Map
id="map"
RTLTextPlugin={false}
ref={onMapReferenceChange}
initialViewState={{
Expand Down Expand Up @@ -382,7 +382,6 @@ export default function MapPage({ onMapLoad }: MapPageProps): ReactElement {
<CustomLayer>
<SolarLayer />
</CustomLayer>
<ZoomControls />
</Map>
);
}
52 changes: 0 additions & 52 deletions web/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -83,58 +83,6 @@
transform: translateX(var(--radix-toast-swipe-end-x));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So nice to get rid of all this crap :D

}

@media screen and (max-width: 768px) {
.maplibregl-ctrl {
display: none !important;
}
}

button.maplibregl-ctrl-zoom-in.maplibregl-ctrl-zoom-in {
width: 33px;
height: 32px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
button.maplibregl-ctrl-zoom-out.maplibregl-ctrl-zoom-out {
width: 33px;
height: 32px;
border-color: lightgray;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}

.maplibregl-ctrl.maplibregl-ctrl-group.maplibregl-ctrl.maplibregl-ctrl-group {
background: transparent;
@apply backdrop-blur-sm;
}
.maplibregl-ctrl.maplibregl-ctrl-group.maplibregl-ctrl.maplibregl-ctrl-group button {
@apply bg-white/80;
}
.maplibregl-ctrl.maplibregl-ctrl-group.maplibregl-ctrl.maplibregl-ctrl-group
button:not(:disabled):hover {
@apply bg-white;
}
.dark button.maplibregl-ctrl-zoom-out.maplibregl-ctrl-zoom-out {
border-color: theme(colors.gray.600);
}
.dark
.maplibregl-ctrl.maplibregl-ctrl-group.maplibregl-ctrl.maplibregl-ctrl-group
button {
@apply bg-gray-800/80;
}

.dark
.maplibregl-ctrl.maplibregl-ctrl-group.maplibregl-ctrl.maplibregl-ctrl-group
button:not(:disabled):hover {
@apply bg-gray-900/90;
}
.dark button.maplibregl-ctrl-zoom-in.maplibregl-ctrl-zoom-in > span {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23FFF'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E") !important;
}
.dark button.maplibregl-ctrl-zoom-out.maplibregl-ctrl-zoom-out > span {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23FFF'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E") !important;
}

html,
body,
#root {
Expand Down
10 changes: 6 additions & 4 deletions web/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Init CSS
import 'react-spring-bottom-sheet/dist/style.css';
import 'maplibre-gl/dist/maplibre-gl.css';
import './index.css';

import * as Sentry from '@sentry/react';
Expand All @@ -9,6 +8,7 @@ import App from 'App';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { I18nextProvider } from 'react-i18next';
import { MapProvider } from 'react-map-gl/maplibre';
import { BrowserRouter } from 'react-router-dom';
import i18n from 'translation/i18n';
import { createConsoleGreeting } from 'utils/createConsoleGreeting';
Expand Down Expand Up @@ -60,9 +60,11 @@ if (container) {
<StrictMode>
<I18nextProvider i18n={i18n}>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<App />
</BrowserRouter>
<MapProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</MapProvider>
</QueryClientProvider>
</I18nextProvider>
</StrictMode>
Expand Down
Loading