diff --git a/packages_rs/nextclade-web/package.json b/packages_rs/nextclade-web/package.json index c4a7c2969..5adc69e4c 100644 --- a/packages_rs/nextclade-web/package.json +++ b/packages_rs/nextclade-web/package.json @@ -86,6 +86,7 @@ "types:generate": "yarn run:node:prod tools/generateTypes.js -i src/gen/_SchemaRoot.json -o src/gen/_SchemaRoot.d.ts" }, "dependencies": { + "@floating-ui/react": "0.26.1", "animate.css": "4.1.1", "auspice": "2.51.0", "autoprefixer": "10.4.5", diff --git a/packages_rs/nextclade-web/src/components/Common/CardCollapsible.tsx b/packages_rs/nextclade-web/src/components/Common/CardCollapsible.tsx index 0a55f3e6e..caf78560d 100644 --- a/packages_rs/nextclade-web/src/components/Common/CardCollapsible.tsx +++ b/packages_rs/nextclade-web/src/components/Common/CardCollapsible.tsx @@ -11,7 +11,7 @@ export interface CardCollapsibleProps extends PropsWithChildren { } export function CardCollapsible({ header, children, defaultCollapsed = true, ...restProps }: CardCollapsibleProps) { - const [collapsed, toggleCollapsed] = useToggle(defaultCollapsed) + const { state: collapsed, toggle: toggleCollapsed } = useToggle(defaultCollapsed) const isOpen = useMemo(() => !collapsed && !!children, [children, collapsed]) const Icon = useMemo( () => , diff --git a/packages_rs/nextclade-web/src/components/Common/InfoButton.tsx b/packages_rs/nextclade-web/src/components/Common/InfoButton.tsx new file mode 100644 index 000000000..6d079fa97 --- /dev/null +++ b/packages_rs/nextclade-web/src/components/Common/InfoButton.tsx @@ -0,0 +1,87 @@ +import React, { PropsWithChildren } from 'react' +import { Button as ButtonBase, CardBody } from 'reactstrap' +import styled, { useTheme } from 'styled-components' +import { FaInfo as InfoIcon } from 'react-icons/fa6' +import { useTranslationSafe } from 'src/helpers/useTranslationSafe' +import { useToggle } from 'src/hooks/useToggle' +import { + useFloating, + useInteractions, + useFocus, + useDismiss, + useRole, + FloatingPortal, + autoPlacement, +} from '@floating-ui/react' + +export interface InfoButtonProps { + size?: number +} + +export function InfoButton({ size = 18, children }: PropsWithChildren) { + const { t } = useTranslationSafe() + const theme = useTheme() + + const { state: isOpen, toggle, setState: setIsOpen } = useToggle(false) + const { refs, context, floatingStyles } = useFloating({ + open: isOpen, + onOpenChange: setIsOpen, + middleware: [autoPlacement()], + }) + const dismiss = useDismiss(context) + const focus = useFocus(context) + const role = useRole(context) + const { getReferenceProps, getFloatingProps } = useInteractions([focus, dismiss, role]) + + return ( + <> + + {isOpen && ( + + + + {children} + + + + )} + + ) +} + +const Floating = styled.div` + z-index: 1005; + width: 500px; + max-width: 80vw; + max-height: 80vh; +` + +const Card = styled.div` + overflow-y: auto; + box-shadow: 1px 1px 10px 5px #0005; + border-radius: 5px; + height: 100%; + background-color: ${(props) => props.theme.bodyBg}; +` + +const Button = styled.button<{ $size?: number }>` + display: flex; + width: ${(props) => props.$size}px; + height: ${(props) => props.$size}px; + border-radius: ${(props) => props.$size}px; + padding: 0 !important; + margin: auto 0.5rem; +` + +const Icon = styled(InfoIcon)` + padding: 0 !important; + margin: auto !important; +` diff --git a/packages_rs/nextclade-web/src/components/Help/SelectDatasetHelp.tsx b/packages_rs/nextclade-web/src/components/Help/SelectDatasetHelp.tsx new file mode 100644 index 000000000..953417c9f --- /dev/null +++ b/packages_rs/nextclade-web/src/components/Help/SelectDatasetHelp.tsx @@ -0,0 +1,44 @@ +import React from 'react' +import { useTranslationSafe } from 'src/helpers/useTranslationSafe' +import { InfoButton } from 'src/components/Common/InfoButton' +import { LinkExternal } from 'src/components/Link/LinkExternal' + +export function SelectDatasetHelp() { + const { t } = useTranslationSafe() + + return ( + +

+ {t( + 'Nextclade software is built to be agnostic to pathogens it analyzes. The information about concrete pathogens is provided in the form of so-called Nextclade datasets.', + )} +

+ +

+ {t( + 'Datasets vary by the pathogen, strain and other attributes. Each dataset is based on a particular reference sequence. Certain datasets only have enough information for basic analysis, others - more information to allow for more in-depth analysis and checks. Dataset authors periodically update and improve their datasets.', + )} +

+ +

+ {t( + 'You can select one of the datasets manually or to use automatic dataset suggestion function. Automatic suggestion will attempt to guess the most appropriate dataset from your sequence data.', + )} +

+ +

+ {t( + "If you don't find a dataset for a pathogen or a strain you need, then you can create your own dataset. You can also publish it to our community collection, so that other people can use it too.", + )} +

+ +

+ {t('Learn more about Nextclade datasets in the {{documentation}}')} + + {t('documentation')} + + {t('.')} +

+
+ ) +} diff --git a/packages_rs/nextclade-web/src/components/Main/DatasetSelector.tsx b/packages_rs/nextclade-web/src/components/Main/DatasetSelector.tsx index 227692a61..8f2c44767 100644 --- a/packages_rs/nextclade-web/src/components/Main/DatasetSelector.tsx +++ b/packages_rs/nextclade-web/src/components/Main/DatasetSelector.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react' import { useRecoilState } from 'recoil' import { Container as ContainerBase } from 'reactstrap' +import { SelectDatasetHelp } from 'src/components/Help/SelectDatasetHelp' import { DatasetSelectorList } from 'src/components/Main/DatasetSelectorList' import { SuggestionPanel } from 'src/components/Main/SuggestionPanel' import { useDatasetSuggestionResults } from 'src/hooks/useRunSeqAutodetect' @@ -56,7 +57,10 @@ export function DatasetSelectorImpl({ return (
- {t('Select dataset')} + + <H4Inline>{t('Select dataset')}</H4Inline> + <SelectDatasetHelp /> +
@@ -104,6 +108,11 @@ const Main = styled.div` ` const Title = styled.h4` + display: flex; flex: 1; +` + +const H4Inline = styled.h4` + display: inline-flex; margin: auto 0; ` diff --git a/packages_rs/nextclade-web/src/components/Main/MainInputForm.tsx b/packages_rs/nextclade-web/src/components/Main/MainInputForm.tsx index bd81f26a7..e51177de5 100644 --- a/packages_rs/nextclade-web/src/components/Main/MainInputForm.tsx +++ b/packages_rs/nextclade-web/src/components/Main/MainInputForm.tsx @@ -2,12 +2,13 @@ import { useRouter } from 'next/router' import React, { useCallback, useEffect, useMemo } from 'react' import { isNil } from 'lodash' import { useRecoilState, useRecoilValue } from 'recoil' +import styled from 'styled-components' +import { SelectDatasetHelp } from 'src/components/Help/SelectDatasetHelp' import { ButtonRun } from 'src/components/Main/ButtonRun' import { SuggestionPanel } from 'src/components/Main/SuggestionPanel' import { useRunAnalysis } from 'src/hooks/useRunAnalysis' import { AutodetectRunState, autodetectRunStateAtom } from 'src/state/autodetect.state' import { shouldSuggestDatasetsOnDatasetPageAtom } from 'src/state/settings.state' -import styled from 'styled-components' import { hasRequiredInputsAtom } from 'src/state/inputs.state' import { datasetCurrentAtom } from 'src/state/dataset.state' import { DatasetCurrentSummary } from 'src/components/Main/DatasetCurrentSummary' @@ -133,7 +134,10 @@ function DatasetCurrentOrSelectButton({ toDatasetSelection }: DatasetCurrentOrSe return (
-

{text}

+ + <H4Inline>{text}</H4Inline> + <SelectDatasetHelp /> +
@@ -150,7 +154,10 @@ function DatasetCurrentOrSelectButton({ toDatasetSelection }: DatasetCurrentOrSe return (
-

{text}

+ + <H4Inline>{text}</H4Inline> + <SelectDatasetHelp /> +
@@ -168,3 +175,13 @@ function DatasetCurrentOrSelectButton({ toDatasetSelection }: DatasetCurrentOrSe ) } + +const Title = styled.h4` + display: flex; + flex: 1; +` + +const H4Inline = styled.h4` + display: inline-flex; + margin: auto 0; +` diff --git a/packages_rs/nextclade-web/src/hooks/useToggle.ts b/packages_rs/nextclade-web/src/hooks/useToggle.ts index da562079c..ce807e935 100644 --- a/packages_rs/nextclade-web/src/hooks/useToggle.ts +++ b/packages_rs/nextclade-web/src/hooks/useToggle.ts @@ -3,12 +3,12 @@ import { RecoilState, useRecoilState } from 'recoil' export type VoidFunc = () => void -export function useToggle(initialState = false): [boolean, VoidFunc, VoidFunc, VoidFunc] { +export function useToggle(initialState = false) { const [state, setState] = useState(initialState) const toggle = useCallback(() => setState((state) => !state), []) const enable = useCallback(() => setState(true), []) const disable = useCallback(() => setState(false), []) - return [state, toggle, enable, disable] + return { state, setState, toggle, enable, disable } } export function useRecoilToggle(recoilState: RecoilState) { diff --git a/packages_rs/nextclade-web/yarn.lock b/packages_rs/nextclade-web/yarn.lock index c57aa3353..a3c29efe9 100644 --- a/packages_rs/nextclade-web/yarn.lock +++ b/packages_rs/nextclade-web/yarn.lock @@ -2074,6 +2074,42 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@floating-ui/core@^1.4.2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.0.tgz#5c05c60d5ae2d05101c3021c1a2a350ddc027f8c" + integrity sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg== + dependencies: + "@floating-ui/utils" "^0.1.3" + +"@floating-ui/dom@^1.5.1": + version "1.5.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.3.tgz#54e50efcb432c06c23cd33de2b575102005436fa" + integrity sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA== + dependencies: + "@floating-ui/core" "^1.4.2" + "@floating-ui/utils" "^0.1.3" + +"@floating-ui/react-dom@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.2.tgz#fab244d64db08e6bed7be4b5fcce65315ef44d20" + integrity sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ== + dependencies: + "@floating-ui/dom" "^1.5.1" + +"@floating-ui/react@0.26.1": + version "0.26.1" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.1.tgz#a790bd3cff5f334d9e87d3ec132a3dc39d937800" + integrity sha512-5gyJIJ2tZOPMgmZ/vEcVhdmQiy75b7LPO71sYIiDsxGcZ4hxLuygQWCuT0YXHqppt//Eese+L6t5KnX/gZ3tVA== + dependencies: + "@floating-ui/react-dom" "^2.0.2" + "@floating-ui/utils" "^0.1.5" + tabbable "^6.0.1" + +"@floating-ui/utils@^0.1.3", "@floating-ui/utils@^0.1.5": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.6.tgz#22958c042e10b67463997bd6ea7115fe28cbcaf9" + integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A== + "@google-cloud/common@^3.0.0": version "3.10.0" resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-3.10.0.tgz#454d1155bb512109cd83c6183aabbd39f9aabda7" @@ -15425,6 +15461,11 @@ synesthesia@^1.0.1: dependencies: css-color-names "0.0.3" +tabbable@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" + integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== + table@^6.6.0, table@^6.8.0: version "6.8.0" resolved "https://registry.yarnpkg.com/table/-/table-6.8.0.tgz#87e28f14fa4321c3377ba286f07b79b281a3b3ca"