mirror of
https://gitlab.nic.cz/turris/reforis/foris-js.git
synced 2024-12-25 00:11:36 +01:00
Merge branch 'dev' into 'master'
Dev See merge request turris/reforis/foris-js!240
This commit is contained in:
commit
a352f12279
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -8,6 +8,19 @@ and this project adheres to
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [6.2.0] - 2024-09-20
|
||||
|
||||
### Added
|
||||
|
||||
- Added useFocusTrap hook
|
||||
- Added extendSession endpoint
|
||||
|
||||
### Changed
|
||||
|
||||
- Refactored Spinner.css to use CSS variable for color
|
||||
- Refactored Modal component to use useFocusTrap hook
|
||||
- Refactored Alert component to use useFocusTrap hook
|
||||
|
||||
## [6.1.1] - 2024-08-30
|
||||
|
||||
### Added
|
||||
|
@ -361,7 +374,8 @@ and this project adheres to
|
|||
## [0.0.7] - 2019-09-02
|
||||
|
||||
[unreleased]:
|
||||
https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.1.1...master
|
||||
https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.2.0...dev
|
||||
[6.2.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.1.1...v6.2.0
|
||||
[6.1.1]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.1.0...v6.1.1
|
||||
[6.1.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.0.3...v6.1.0
|
||||
[6.0.3]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.0.2...v6.0.3
|
||||
|
|
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "foris",
|
||||
"version": "6.1.1",
|
||||
"version": "6.2.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "foris",
|
||||
"version": "6.1.1",
|
||||
"version": "6.2.0",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "foris",
|
||||
"version": "6.1.1",
|
||||
"version": "6.2.0",
|
||||
"description": "Foris JS library is a set of components and utils for reForis application and plugins.",
|
||||
"author": "CZ.NIC, z.s.p.o.",
|
||||
"repository": {
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
* See /LICENSE for more information.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { useRef } from "react";
|
||||
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { useFocusTrap } from "../utils/hooks";
|
||||
|
||||
export const ALERT_TYPES = Object.freeze({
|
||||
PRIMARY: "primary",
|
||||
SECONDARY: "secondary",
|
||||
|
@ -37,11 +39,15 @@ Alert.defaultProps = {
|
|||
};
|
||||
|
||||
function Alert({ type, onDismiss, children }) {
|
||||
const alertRef = useRef();
|
||||
useFocusTrap(alertRef, !!onDismiss);
|
||||
return (
|
||||
<div
|
||||
ref={alertRef}
|
||||
className={`alert alert-${type} ${
|
||||
onDismiss ? "alert-dismissible" : ""
|
||||
}`.trim()}
|
||||
role="alert"
|
||||
>
|
||||
{onDismiss && (
|
||||
<button
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, { useRef, useEffect } from "react";
|
|||
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { useClickOutside } from "../utils/hooks";
|
||||
import { useClickOutside, useFocusTrap } from "../utils/hooks";
|
||||
import Portal from "../utils/Portal";
|
||||
import "./Modal.css";
|
||||
|
||||
|
@ -29,10 +29,11 @@ Modal.propTypes = {
|
|||
};
|
||||
|
||||
export function Modal({ shown, setShown, scrollable, size, children }) {
|
||||
const dialogRef = useRef();
|
||||
const modalRef = useRef();
|
||||
let modalSize = "modal-";
|
||||
|
||||
useClickOutside(dialogRef, () => setShown(false));
|
||||
useClickOutside(modalRef, () => setShown(false));
|
||||
useFocusTrap(modalRef, shown);
|
||||
|
||||
useEffect(() => {
|
||||
const handleEsc = (event) => {
|
||||
|
@ -65,11 +66,13 @@ export function Modal({ shown, setShown, scrollable, size, children }) {
|
|||
return (
|
||||
<Portal containerId="modal-container">
|
||||
<div
|
||||
ref={modalRef}
|
||||
className={`modal fade ${shown ? "show" : ""}`.trim()}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="modal-title"
|
||||
>
|
||||
<div
|
||||
ref={dialogRef}
|
||||
className={`${modalSize.trim()} modal-dialog modal-dialog-centered ${
|
||||
scrollable ? "modal-dialog-scrollable" : ""
|
||||
}`.trim()}
|
||||
|
@ -90,7 +93,7 @@ ModalHeader.propTypes = {
|
|||
export function ModalHeader({ setShown, title }) {
|
||||
return (
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">{title}</h5>
|
||||
<h1 className="modal-title fs-5">{title}</h1>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
.spinner-wrapper .spinner-border {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
color: #00a2e2;
|
||||
color: var(--bs-primary);
|
||||
}
|
||||
|
||||
.spinner-fs-background {
|
||||
|
|
|
@ -6,6 +6,8 @@ exports[`<RebootButton/> Render modal. 1`] = `
|
|||
id="modal-container"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="modal-title"
|
||||
aria-modal="true"
|
||||
class="modal fade show"
|
||||
role="dialog"
|
||||
>
|
||||
|
@ -19,11 +21,11 @@ exports[`<RebootButton/> Render modal. 1`] = `
|
|||
<div
|
||||
class="modal-header"
|
||||
>
|
||||
<h5
|
||||
class="modal-title"
|
||||
<h1
|
||||
class="modal-title fs-5"
|
||||
>
|
||||
Warning!
|
||||
</h5>
|
||||
</h1>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="btn-close"
|
||||
|
|
|
@ -7,6 +7,7 @@ exports[`AlertContext should render alert 1`] = `
|
|||
>
|
||||
<div
|
||||
class="alert alert-danger alert-dismissible"
|
||||
role="alert"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/)
|
||||
* Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
|
||||
*
|
||||
* This is free software, licensed under the GNU General Public License v3.
|
||||
* See /LICENSE for more information.
|
||||
|
@ -9,8 +9,10 @@ export const REFORIS_URL_PREFIX = "/reforis";
|
|||
export const REFORIS_API_URL_PREFIX = `${REFORIS_URL_PREFIX}/api`;
|
||||
|
||||
export const ForisURLs = {
|
||||
// turris-auth
|
||||
login: `/login?${REFORIS_URL_PREFIX}/`,
|
||||
logout: `/logout`,
|
||||
extendSession: `/extend-session`,
|
||||
|
||||
static: `${REFORIS_URL_PREFIX}/static/reforis`,
|
||||
wifi: `${REFORIS_URL_PREFIX}/network-settings/wifi`,
|
||||
|
|
|
@ -40,3 +40,40 @@ export function useClickOutside(element, callback) {
|
|||
};
|
||||
});
|
||||
}
|
||||
|
||||
/* Trap focus inside element. */
|
||||
export function useFocusTrap(elementRef, condition = true) {
|
||||
useEffect(() => {
|
||||
if (!condition) {
|
||||
return;
|
||||
}
|
||||
const currentElement = elementRef.current;
|
||||
const focusableElements = currentElement.querySelectorAll(
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||
);
|
||||
const firstElement = focusableElements[0];
|
||||
const lastElement = focusableElements[focusableElements.length - 1];
|
||||
|
||||
const handleTab = (event) => {
|
||||
if (event.key === "Tab") {
|
||||
if (event.shiftKey) {
|
||||
if (document.activeElement === firstElement) {
|
||||
lastElement.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
} else if (document.activeElement === lastElement) {
|
||||
firstElement.focus();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
currentElement.addEventListener("keydown", handleTab);
|
||||
|
||||
firstElement.focus();
|
||||
|
||||
return () => {
|
||||
currentElement.removeEventListener("keydown", handleTab);
|
||||
};
|
||||
}, [elementRef, condition]);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user