diff --git a/app/frontend/src/javascript/components/base/fab-tabs.tsx b/app/frontend/src/javascript/components/base/fab-tabs.tsx index 09c3d47fd..ef404f226 100644 --- a/app/frontend/src/javascript/components/base/fab-tabs.tsx +++ b/app/frontend/src/javascript/components/base/fab-tabs.tsx @@ -1,4 +1,5 @@ import { ReactNode, useEffect, useState } from 'react'; +import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; import * as React from 'react'; import _ from 'lodash'; import { usePrevious } from '../../lib/use-previous'; @@ -19,7 +20,7 @@ interface FabTabsProps { } /** - * Tabulation system + * A wrapper around https://github.com/reactjs/react-tabs that provides the Fab-manager's theme for tabs */ export const FabTabs: React.FC = ({ tabs, defaultTab, className }) => { const [active, setActive] = useState(tabs.filter(Boolean).find(t => t.id === defaultTab) || tabs.filter(Boolean)[0]); @@ -32,21 +33,28 @@ export const FabTabs: React.FC = ({ tabs, defaultTab, className }) }, [tabs]); /** - * Callback triggered when a tab a selected + * Return the index of the currently selected tabs (i.e. the "active" tab) */ - const onTabSelected = (tab: Tab) => { - setActive(tab); - if (typeof tab.onSelected === 'function') tab.onSelected(); + const selectedIndex = (): number => { + return tabs.findIndex(t => t?.id === active?.id) || 0; + }; + + /** + * Callback triggered when the active tab is changed by the user + */ + const onIndexSelected = (index: number) => { + setActive(tabs[index]); + if (typeof tabs[index].onSelected === 'function') { + tabs[index].onSelected(); + } }; return ( -
-
- {tabs.filter(Boolean).map((tab, index) => ( -

onTabSelected(tab)}>{tab.title}

- ))} -
- {active?.content} -
+ + + {tabs.filter(Boolean).map((tab, index) => {tab.title})} + + {tabs.filter(Boolean).map((tab, index) => {tab.content})} + ); }; diff --git a/app/frontend/src/stylesheets/modules/base/fab-tabs.scss b/app/frontend/src/stylesheets/modules/base/fab-tabs.scss index d2d5b38f7..c53fabd24 100644 --- a/app/frontend/src/stylesheets/modules/base/fab-tabs.scss +++ b/app/frontend/src/stylesheets/modules/base/fab-tabs.scss @@ -2,8 +2,10 @@ .tabs { display: flex; justify-content: space-between; + list-style: none; + padding-left: 0; - p { + li { flex: 1; margin-bottom: 4rem; padding: 0.8rem; @@ -11,7 +13,7 @@ color: var(--main); border-bottom: 1px solid var(--gray-soft-dark); - &.is-active { + &.react-tabs__tab--selected { color: var(--gray-hard-dark); border: 1px solid var(--gray-soft-dark); border-bottom: none; diff --git a/package.json b/package.json index ad26a0c6d..24bbfea2e 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "license": "AGPL-3.0-only", "devDependencies": { "@pmmmwh/react-refresh-webpack-plugin": "^0.5.4", + "@testing-library/dom": ">=7.21.8", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "12", "@testing-library/user-event": "^14.4.3", @@ -34,7 +35,7 @@ "eslint-plugin-html-erb": "^1.0.1", "eslint-plugin-import": "~2.25.4", "eslint-plugin-jest": "^27.1.6", - "eslint-plugin-n": "^15.1.0", + "eslint-plugin-n": "^14.0.0", "eslint-plugin-promise": "~6.0.0", "eslint-plugin-react": "^7.29.4", "eslint-webpack-plugin": "^3.1.1", @@ -78,6 +79,7 @@ "@types/prop-types": "^15.7.2", "@types/react": "^17.0.3", "@types/react-dom": "^17.0.3", + "@types/sortablejs": "1", "@uirouter/angularjs": "1.0.30", "AngularDevise": "https://github.com/cloudspace/angular_devise.git#1.0.2", "angular": "1.8", @@ -157,6 +159,7 @@ "react-select": "^5.3.2", "react-sortablejs": "^6.1.4", "react-switch": "^6.0.0", + "react-tabs": "4", "react2angular": "^4.0.6", "resolve-url-loader": "^4.0.0", "sass": "^1.49.9", diff --git a/yarn.lock b/yarn.lock index 8430d1ecf..6c1439166 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2876,7 +2876,7 @@ "@svgr/plugin-jsx" "^6.2.1" "@svgr/plugin-svgo" "^6.2.0" -"@testing-library/dom@>=7", "@testing-library/dom@^8.0.0": +"@testing-library/dom@>=7", "@testing-library/dom@>=7.21.8", "@testing-library/dom@^8.0.0": version "8.19.0" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.19.0.tgz#bd3f83c217ebac16694329e413d9ad5fdcfd785f" integrity sha512-6YWYPPpxG3e/xOo6HIWwB/58HukkwIVTOaZ0VwdMVjhRUX/01E4FtQbck9GazOOj7MXHc5RBzMrU86iBJHbI+A== @@ -3478,6 +3478,11 @@ dependencies: "@types/node" "*" +"@types/sortablejs@1": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@types/sortablejs/-/sortablejs-1.15.0.tgz#695e481752e2a0a311c5e73b51d5f666fc202f93" + integrity sha512-qrhtM7M41EhH4tZQTNw2/RJkxllBx3reiJpTbgWCM2Dx0U1sZ6LwKp9lfNln9uqE26ZMKUaPEYaD4rzvOWYtZw== + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -4582,13 +4587,6 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" -builtins@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/builtins/-/builtins-4.1.0.tgz#1edd016dd91ce771a1ed6fc3b2b71fb918953250" - integrity sha512-1bPRZQtmKaO6h7qV1YHXNtr6nCK28k0Zo95KM4dXfILcZZwoHJBN1m3lfLv9LPkcOZlrSr+J1bzMaZFO98Yq0w== - dependencies: - semver "^7.0.0" - bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -4785,6 +4783,11 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== +clsx@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -5759,19 +5762,18 @@ eslint-plugin-jest@^27.1.6: dependencies: "@typescript-eslint/utils" "^5.10.0" -eslint-plugin-n@^15.1.0: - version "15.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-n/-/eslint-plugin-n-15.1.0.tgz#efb6648dda91bc00e1521f42e5c0208eeae82bb8" - integrity sha512-Tgx4Z58QXv2Ha7Qzp0u4wavnZNZ3AOievZMxrAxi7nvDbzD5B/JqOD80LHYcGHFZc2HD9jDmM/+KWMPov46a4A== +eslint-plugin-n@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-n/-/eslint-plugin-n-14.0.0.tgz#aa7944f5b1fd69cf64ecb1ddd1a4762bb0f4f352" + integrity sha512-mNwplPLsbaKhHyA0fa/cy8j+oF6bF6l81hzBTWa6JOvPcMNAuIogk2ih6d9tYvWYzyUG+7ZFeChqbzdFpg2QrQ== dependencies: - builtins "^4.0.0" eslint-plugin-es "^4.1.0" eslint-utils "^3.0.0" ignore "^5.1.1" is-core-module "^2.3.0" minimatch "^3.0.4" resolve "^1.10.1" - semver "^6.3.0" + semver "^6.1.0" eslint-plugin-promise@~6.0.0: version "6.0.0" @@ -8932,6 +8934,15 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +prop-types@^15.5.0, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" @@ -8941,15 +8952,6 @@ prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" -prop-types@^15.8.1: - version "15.8.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" - integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.13.1" - prosemirror-commands@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.3.1.tgz#926c88801eebaa50363d4658850b41406d375a31" @@ -9212,6 +9214,14 @@ react-switch@^6.0.0: dependencies: prop-types "^15.7.2" +react-tabs@4: + version "4.2.1" + resolved "https://registry.yarnpkg.com/react-tabs/-/react-tabs-4.2.1.tgz#82e2dc787555e791f909746dd64164d780d7d477" + integrity sha512-nQcEN3KrAsSry6f9Jz2oyMQsnh+sLEy31YjlskL/mnI3KU/c7BeyD1VzHZmmcJ15UEFu12pYOXYkdTzZ0uyIbw== + dependencies: + clsx "^1.1.0" + prop-types "^15.5.0" + react-test-renderer@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c" @@ -9680,12 +9690,12 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.0.0, semver@^7.3.5: +semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==