Skip to content
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "react-spatial",
"license": "MIT",
"description": "Components to build React map apps.",
"version": "1.11.0",
"version": "1.11.1-beta.2",
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
Expand Down
165 changes: 99 additions & 66 deletions src/components/BaseLayerSwitcher/BaseLayerSwitcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,24 @@ const propTypes = {
* @param {function} Translation function returning the translated string.
*/
t: PropTypes.func,
};

const defaultProps = {
className: "rs-base-layer-switcher",
altText: "Source not found",
titles: {
button: "Base layers",
openSwitcher: "Open Baselayer-Switcher",
closeSwitcher: "Close Baselayer-Switcher",
},
closeButtonImage: <FaChevronLeft />,
layerImages: undefined,
t: (s) => {
return s;
},
/**
* Callback function on close button click.
* @param {function} Callback function triggered when a switcher button is clicked. Takes the event as argument.
*/
onCloseButtonClick: PropTypes.func,

/**
* Callback function on layer button click.
* @param {function} Callback function triggered when a switcher button is clicked. Takes the event and the layer as arguments.
*/
onLayerButtonClick: PropTypes.func,

/**
* Callback function on main switcher button click.
* @param {function} Callback function triggered when a switcher button is clicked. Takes the event as argument.
*/
onSwitcherButtonClick: PropTypes.func,
};

const getVisibleLayer = (layers) => {
Expand Down Expand Up @@ -90,6 +93,31 @@ const getImageStyle = (url) => {
: null;
};

function CloseButton({ onClick, tabIndex, title, children }) {
return (
<div
className="rs-base-layer-switcher-close-btn"
role="button"
onClick={onClick}
onKeyPress={(e) => {
return e.which === 13 && onClick();
}}
tabIndex={tabIndex}
aria-label={title}
title={title}
>
{children}
</div>
);
}

CloseButton.propTypes = {
onClick: PropTypes.func.isRequired,
tabIndex: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
};

/**
* The BaseLayerSwitcher component renders a button interface for switching the visible
* [mobility-toolbox-js layer](https://mobility-toolbox-js.geops.io/api/identifiers%20html#ol-layers)
Expand All @@ -98,33 +126,26 @@ const getImageStyle = (url) => {

function BaseLayerSwitcher({
layers,
layerImages,
className,
altText,
titles,
closeButtonImage,
t,
layerImages = undefined,
className = "rs-base-layer-switcher",
altText = "Source not found",
titles = {
button: "Base layers",
openSwitcher: "Open Baselayer-Switcher",
closeSwitcher: "Close Baselayer-Switcher",
},
closeButtonImage = <FaChevronLeft />,
onCloseButtonClick = null,
onLayerButtonClick = null,
onSwitcherButtonClick = null,
t = (s) => s,
}) {
const [switcherOpen, setSwitcherOpen] = useState(false);
const [isClosed, setIsClosed] = useState(true);
const [currentLayer, setCurrentLayer] = useState(
getVisibleLayer(layers) || layers[0],
);

useEffect(() => {
// Update the layer selected when a visibility changes.
const olKeys = (layers || []).map((layer) => {
return layer.on("change:visible", (evt) => {
if (evt.target.visible && currentLayer !== evt.target) {
setCurrentLayer(evt.target);
}
});
});
return () => {
unByKey(olKeys);
};
}, [currentLayer, layers]);

/* Images are loaded from props if provided, fallback from layer */
const images = layerImages
? Object.keys(layerImages).map((layerImage) => {
Expand All @@ -137,12 +158,17 @@ function BaseLayerSwitcher({
const openClass = switcherOpen ? " rs-open" : "";
const hiddenStyle = switcherOpen && !isClosed ? "visible" : "hidden";

const handleSwitcherClick = () => {
const handleSwitcherClick = (evt) => {
const nextLayer = layers.find((layer) => {
return !layer.visible;
});
const onButtonClick =
layers.length === 2 ? onLayerButtonClick : onSwitcherButtonClick;
if (onButtonClick) {
onButtonClick(evt, nextLayer);
}
if (layers.length === 2) {
/* On only two layer options the opener becomes a layer toggle button */
const nextLayer = layers.find((layer) => {
return !layer.visible;
});
if (currentLayer.setVisible) {
currentLayer.setVisible(false);
} else {
Expand All @@ -160,7 +186,10 @@ function BaseLayerSwitcher({
return setSwitcherOpen(true) && setIsClosed(false);
};

const onLayerSelect = (layer) => {
const onLayerSelect = (layer, evt) => {
if (onLayerButtonClick) {
onLayerButtonClick(evt, layer);
}
if (!switcherOpen) {
setSwitcherOpen(true);
return;
Expand Down Expand Up @@ -214,30 +243,24 @@ function BaseLayerSwitcher({
};
}, [switcherOpen]);

useEffect(() => {
// Update the layer selected when a visibility changes.
const olKeys = (layers || []).map((layer) => {
return layer.on("change:visible", (evt) => {
if (evt.target.visible && currentLayer !== evt.target) {
setCurrentLayer(evt.target);
}
});
});
return () => {
unByKey(olKeys);
};
}, [currentLayer, layers]);

if (!layers || layers.length < 2 || !currentLayer) {
return null;
}

const toggleBtn = (
<div className="rs-base-layer-switcher-btn-wrapper">
<div
className="rs-base-layer-switcher-close-btn"
role="button"
onClick={() => {
return setSwitcherOpen(false);
}}
onKeyPress={(e) => {
return e.which === 13 && setSwitcherOpen(false);
}}
tabIndex={switcherOpen ? "0" : "-1"}
aria-label={titles.closeSwitcher}
title={titles.closeSwitcher}
>
{closeButtonImage}
</div>
</div>
);

return (
<div className={`${className}${openClass}`}>
<div
Expand Down Expand Up @@ -290,12 +313,12 @@ function BaseLayerSwitcher({
role="button"
title={t(layerName)}
aria-label={t(layerName)}
onClick={() => {
return onLayerSelect(layer);
onClick={(evt) => {
return onLayerSelect(layer, evt);
}}
onKeyPress={(e) => {
if (e.which === 13) {
onLayerSelect(layer);
onKeyPress={(evt) => {
if (evt.which === 13) {
onLayerSelect(layer, evt);
}
}}
style={imageStyle}
Expand All @@ -311,12 +334,22 @@ function BaseLayerSwitcher({
</div>
);
})}
{toggleBtn}
<CloseButton
onClick={(evt) => {
if (onCloseButtonClick) {
onCloseButtonClick(evt);
}
setSwitcherOpen(false);
}}
tabIndex={switcherOpen ? "0" : "-1"}
title={titles.closeSwitcher}
>
{closeButtonImage}
</CloseButton>
</div>
);
}

BaseLayerSwitcher.propTypes = propTypes;
BaseLayerSwitcher.defaultProps = defaultProps;

export default BaseLayerSwitcher;
1 change: 1 addition & 0 deletions src/components/BaseLayerSwitcher/BaseLayerSwitcher.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
transition: 800ms width;
overflow: hidden;
display: flex;
align-items: center;
padding: 2px;
pointer-events: none;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,25 +64,23 @@ exports[`BaseLayerSwitcher matches snapshots using default properties. 1`] = `
</span>
</div>
</div>
<div class="rs-base-layer-switcher-btn-wrapper">
<div class="rs-base-layer-switcher-close-btn"
role="button"
tabindex="-1"
aria-label="Close Baselayer-Switcher"
title="Close Baselayer-Switcher"
<div class="rs-base-layer-switcher-close-btn"
role="button"
tabindex="-1"
aria-label="Close Baselayer-Switcher"
title="Close Baselayer-Switcher"
>
<svg stroke="currentColor"
fill="currentColor"
stroke-width="0"
viewbox="0 0 320 512"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<svg stroke="currentColor"
fill="currentColor"
stroke-width="0"
viewbox="0 0 320 512"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z">
</path>
</svg>
</div>
<path d="M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z">
</path>
</svg>
</div>
</div>
`;
47 changes: 29 additions & 18 deletions src/components/Zoom/Zoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,18 @@ const propTypes = {
* Display a slider to zoom.
*/
zoomSlider: PropTypes.bool,
};

const defaultProps = {
titles: {
zoomIn: "Zoom in",
zoomOut: "Zoom out",
},
zoomInChildren: <FaPlus focusable={false} />,
zoomOutChildren: <FaMinus focusable={false} />,
zoomSlider: false,
delta: 1,
/**
* Callback function on zoom-in button click.
* @param {function} Callback function triggered when zoom-in button is clicked. Takes the event as argument.
*/
onZoomInButtonClick: PropTypes.func,

/**
* Callback function on zoom-out button click.
* @param {function} Callback function triggered when the zoom-out button is clicked. Takes the event as argument.
*/
onZoomOutButtonClick: PropTypes.func,
};

const updateZoom = (map, delta) => {
Expand All @@ -79,32 +80,43 @@ const updateZoom = (map, delta) => {
*/
function Zoom({
map,
titles,
zoomInChildren,
zoomOutChildren,
zoomSlider,
delta,
titles = {
zoomIn: "Zoom in",
zoomOut: "Zoom out",
},
zoomInChildren = <FaPlus focusable={false} />,
zoomOutChildren = <FaMinus focusable={false} />,
zoomSlider = false,
onZoomInButtonClick = null,
onZoomOutButtonClick = null,
delta = 1,
...other
}) {
const ref = useRef();
const [currentZoom, setZoom] = useState();

const zoomIn = useCallback(
(evt) => {
if (onZoomInButtonClick) {
onZoomInButtonClick(evt);
}
if (!evt.which || evt.which === 13) {
updateZoom(map, delta);
}
},
[delta, map],
[delta, map, onZoomInButtonClick],
);

const zoomOut = useCallback(
(evt) => {
if (onZoomOutButtonClick) {
onZoomOutButtonClick(evt);
}
if (!evt.which || evt.which === 13) {
updateZoom(map, -delta);
}
},
[delta, map],
[delta, map, onZoomOutButtonClick],
);

const zoomInDisabled = useMemo(() => {
Expand Down Expand Up @@ -175,6 +187,5 @@ function Zoom({
}

Zoom.propTypes = propTypes;
Zoom.defaultProps = defaultProps;

export default React.memo(Zoom);
Loading