diff --git a/gradle/changelog/accessibility_tabpanel.yaml b/gradle/changelog/accessibility_tabpanel.yaml new file mode 100644 index 0000000000..6d1b044353 --- /dev/null +++ b/gradle/changelog/accessibility_tabpanel.yaml @@ -0,0 +1,4 @@ +- type: fixed + description: Make compare accessible +- type: fixed + description: Compare target when default branch contains slash diff --git a/scm-ui/ui-forms/src/index.ts b/scm-ui/ui-forms/src/index.ts index c609b932ea..40a9a96316 100644 --- a/scm-ui/ui-forms/src/index.ts +++ b/scm-ui/ui-forms/src/index.ts @@ -42,6 +42,7 @@ export { default as ConfigurationForm } from "./ConfigurationForm"; export { default as SelectField } from "./select/SelectField"; export { default as ChipInputField } from "./chip-input/ChipInputField"; export { default as ComboboxField } from "./combobox/ComboboxField"; +export { default as Input } from "./input/Input"; export { default as Select } from "./select/Select"; export * from "./resourceHooks"; diff --git a/scm-ui/ui-layout/package.json b/scm-ui/ui-layout/package.json index 2406a07259..dfb0497b47 100644 --- a/scm-ui/ui-layout/package.json +++ b/scm-ui/ui-layout/package.json @@ -51,6 +51,7 @@ "dependencies": { "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-slot": "^1.0.1", + "@radix-ui/react-tabs": "^1.0.4", "@scm-manager/ui-buttons": "2.46.2-SNAPSHOT" } } diff --git a/scm-ui/ui-layout/src/index.ts b/scm-ui/ui-layout/src/index.ts index e93ae02118..02bdb809f7 100644 --- a/scm-ui/ui-layout/src/index.ts +++ b/scm-ui/ui-layout/src/index.ts @@ -42,6 +42,10 @@ import { DataPageHeaderSettingLabel, DataPageHeaderSettings, } from "./templates/data-page/DataPageHeader"; +import TabsComponent from "./tabs/Tabs"; +import TabsContent from "./tabs/TabsContent"; +import TabsList from "./tabs/TabsList"; +import TabTrigger from "./tabs/TabTrigger"; export { default as Collapsible } from "./collapsible/Collapsible"; @@ -80,3 +84,10 @@ export const DataPageHeader = Object.assign(DataPageHeaderComponent, { }), CreateButton: DataPageHeaderCreateButton, }); + +export const Tabs = Object.assign(TabsComponent, { + List: Object.assign(TabsList, { + Trigger: TabTrigger, + }), + Content: TabsContent, +}); diff --git a/scm-ui/ui-layout/src/tabs/TabTrigger.tsx b/scm-ui/ui-layout/src/tabs/TabTrigger.tsx new file mode 100644 index 0000000000..c3739e985c --- /dev/null +++ b/scm-ui/ui-layout/src/tabs/TabTrigger.tsx @@ -0,0 +1,46 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { ComponentProps } from "react"; +import classNames from "classnames"; +import * as HeadlessTabs from "@radix-ui/react-tabs"; + +type Props = ComponentProps; + +/** + * @beta + * @since 2.47.0 + */ +const TabTrigger = React.forwardRef(({ children, className, ...props }, ref) => ( +
  • + + {children} + +
  • +)); +export default TabTrigger; diff --git a/scm-ui/ui-layout/src/tabs/Tabs.stories.tsx b/scm-ui/ui-layout/src/tabs/Tabs.stories.tsx new file mode 100644 index 0000000000..78d79cb1b1 --- /dev/null +++ b/scm-ui/ui-layout/src/tabs/Tabs.stories.tsx @@ -0,0 +1,48 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from "react"; +import StoryRouter from "storybook-react-router"; +import { ComponentMeta, ComponentStory } from "@storybook/react"; +import Tabs from "./Tabs"; +import TabsList from "./TabsList"; +import TabTrigger from "./TabTrigger"; +import TabsContent from "./TabsContent"; + +export default { + title: "Tab", + component: Tabs, + decorators: [StoryRouter()], +} as ComponentMeta; + +export const Default: ComponentStory = () => ( + + + Account + Password + + Account Settings + Password Settings + +); diff --git a/scm-ui/ui-layout/src/tabs/Tabs.tsx b/scm-ui/ui-layout/src/tabs/Tabs.tsx new file mode 100644 index 0000000000..2d7983ff5a --- /dev/null +++ b/scm-ui/ui-layout/src/tabs/Tabs.tsx @@ -0,0 +1,52 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { ComponentProps, useCallback, useState } from "react"; +import * as HeadlessTabs from "@radix-ui/react-tabs"; + +type Props = Omit, "value">; + +/** + * @beta + * @since 2.47.0 + */ +const Tabs = React.forwardRef(({ defaultValue, onValueChange, children, ...props }, ref) => { + const [value, setValue] = useState(defaultValue); + const handleValueChange = useCallback( + (newValue: string) => { + setValue(newValue); + if (onValueChange) { + onValueChange(newValue); + } + }, + [onValueChange] + ); + return ( + + {children} + + ); +}); + +export default Tabs; diff --git a/scm-ui/ui-layout/src/tabs/TabsContent.tsx b/scm-ui/ui-layout/src/tabs/TabsContent.tsx new file mode 100644 index 0000000000..5b9b21c672 --- /dev/null +++ b/scm-ui/ui-layout/src/tabs/TabsContent.tsx @@ -0,0 +1,33 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import * as RadixTabs from "@radix-ui/react-tabs"; + +/** + * @beta + * @since 2.47.0 + */ +const TabsContent = RadixTabs.Content; + +export default TabsContent; diff --git a/scm-ui/ui-layout/src/tabs/TabsList.tsx b/scm-ui/ui-layout/src/tabs/TabsList.tsx new file mode 100644 index 0000000000..b77262012e --- /dev/null +++ b/scm-ui/ui-layout/src/tabs/TabsList.tsx @@ -0,0 +1,41 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { HTMLAttributes } from "react"; +import * as RadixTabs from "@radix-ui/react-tabs"; +import classNames from "classnames"; + +/** + * @beta + * @since 2.47.0 + */ +const TabsList = React.forwardRef>(({ children, ...props }, ref) => ( + +
      + {children} +
    +
    +)); + +export default TabsList; diff --git a/scm-ui/ui-styles/package.json b/scm-ui/ui-styles/package.json index d08636be1e..3ed70fd7ff 100644 --- a/scm-ui/ui-styles/package.json +++ b/scm-ui/ui-styles/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^5.11.2", - "bulma": "^0.9.3", + "bulma": "https://github.com/scm-manager/bulma#bbe5bdb66fce67e2513ab8caefb1eac5504e1402", "bulma-popover": "^1.0.0", "react-diff-view": "^2.4.10" }, @@ -31,4 +31,4 @@ "eslintConfig": { "extends": "@scm-manager/eslint-config" } -} \ No newline at end of file +} diff --git a/scm-ui/ui-webapp/src/repos/compare/BranchTab.tsx b/scm-ui/ui-webapp/src/repos/compare/BranchTab.tsx new file mode 100644 index 0000000000..1d3ba80d86 --- /dev/null +++ b/scm-ui/ui-webapp/src/repos/compare/BranchTab.tsx @@ -0,0 +1,97 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { FC, useState } from "react"; +import CompareSelectorListEntry from "./CompareSelectorListEntry"; +import DefaultBranchTag from "../branches/components/DefaultBranchTag"; +import { Branch, Repository } from "@scm-manager/ui-types"; +import { CompareFunction, CompareProps, CompareTypes } from "./CompareSelectBar"; +import { useBranches } from "@scm-manager/ui-api"; +import { ErrorNotification, Loading, Notification } from "@scm-manager/ui-components"; +import styled from "styled-components"; +import { useTranslation } from "react-i18next"; + +type Props = { + onSelect: CompareFunction; + selected: CompareProps; + repository: Repository; + filter: string; +}; + +const FixedWidthNotification = styled(Notification)` + width: 18.5rem; + margin-top: 0.5rem; +`; + +const ScrollableUl = styled.ul` + max-height: 15.65rem; + width: 18.5rem; + overflow-x: hidden; + overflow-y: auto; +`; + +const BranchTab: FC = ({ onSelect, selected, repository, filter }) => { + const [t] = useTranslation("repos"); + const { isLoading: branchesIsLoading, error: branchesError, data: branchesData } = useBranches(repository); + const branches: Branch[] = (branchesData?._embedded?.branches as Branch[]) || []; + + const [selection, setSelection] = useState(selected); + + const onSelectEntry = (type: CompareTypes, name: string) => { + setSelection({ type, name }); + onSelect(type, name); + }; + + if (branchesIsLoading) { + return ; + } + if (branchesError) { + return ; + } + + const elements = branches.filter((branch) => branch.name.includes(filter)); + + if (elements.length === 0) { + return {t("compare.selector.emptyResult")}; + } + + return ( + + {elements.map((branch) => { + return ( + onSelectEntry("b", branch.name)} + key={branch.name} + > + {branch.name} + + + ); + })} + + ); +}; + +export default BranchTab; diff --git a/scm-ui/ui-webapp/src/repos/compare/CompareRoot.tsx b/scm-ui/ui-webapp/src/repos/compare/CompareRoot.tsx index 0716ba5a60..a7c63bd607 100644 --- a/scm-ui/ui-webapp/src/repos/compare/CompareRoot.tsx +++ b/scm-ui/ui-webapp/src/repos/compare/CompareRoot.tsx @@ -52,7 +52,10 @@ const CompareRoot: FC = ({ repository, baseUrl }) => { {data._embedded && ( - b.defaultBranch)[0].name}`} /> + b.defaultBranch)[0].name)}`} + /> )} ); diff --git a/scm-ui/ui-webapp/src/repos/compare/CompareSelector.tsx b/scm-ui/ui-webapp/src/repos/compare/CompareSelector.tsx index 980c003d0c..9795543ae4 100644 --- a/scm-ui/ui-webapp/src/repos/compare/CompareSelector.tsx +++ b/scm-ui/ui-webapp/src/repos/compare/CompareSelector.tsx @@ -27,9 +27,13 @@ import { useTranslation } from "react-i18next"; import classNames from "classnames"; import styled from "styled-components"; import { Repository } from "@scm-manager/ui-types"; -import { devices, Icon } from "@scm-manager/ui-components"; -import CompareSelectorList from "./CompareSelectorList"; +import { devices } from "@scm-manager/ui-components"; +import { Tabs } from "@scm-manager/ui-layout"; +import { Icon } from "@scm-manager/ui-buttons"; import { CompareFunction, CompareProps, CompareTypes } from "./CompareSelectBar"; +import BranchTab from "./BranchTab"; +import TagTab from "./TagTab"; +import RevisionTab from "./RevisionTab"; type Props = { onSelect: CompareFunction; @@ -114,7 +118,7 @@ const CompareSelector: FC = ({ onSelect, selected, label, repository }) = {getActionTypeName(selection.type)}: {selection.name} - + angle-down @@ -128,15 +132,25 @@ const CompareSelector: FC = ({ onSelect, selected, label, repository }) = setFilter(e.target.value)} + onChange={(e) => setFilter(e.target.value)} type="search" /> - + + + {t("compare.selector.tabs.b")} + {t("compare.selector.tabs.t")} + {t("compare.selector.tabs.r")} + + + + + + + + + + + diff --git a/scm-ui/ui-webapp/src/repos/compare/CompareSelectorList.tsx b/scm-ui/ui-webapp/src/repos/compare/CompareSelectorList.tsx deleted file mode 100644 index 88217775ce..0000000000 --- a/scm-ui/ui-webapp/src/repos/compare/CompareSelectorList.tsx +++ /dev/null @@ -1,272 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React, { FC, KeyboardEvent, useState } from "react"; -import { useTranslation } from "react-i18next"; -import classNames from "classnames"; -import styled from "styled-components"; -import { Branch, Repository, Tag } from "@scm-manager/ui-types"; -import { useBranches, useTags } from "@scm-manager/ui-api"; -import { Button, ErrorNotification, Loading, NoStyleButton, Notification } from "@scm-manager/ui-components"; -import DefaultBranchTag from "../branches/components/DefaultBranchTag"; -import CompareSelectorListEntry from "./CompareSelectorListEntry"; -import { CompareFunction, CompareProps, CompareTypes } from "./CompareSelectBar"; - -type Props = { - onSelect: CompareFunction; - selected: CompareProps; - repository: Repository; - filter: string; -}; - -const TabStyleButton = styled(NoStyleButton)` - align-items: center; - border-bottom: var(--scm-border); - color: var(--scm-secondary-text); - display: flex; - justify-content: center; - margin-bottom: -1px; - padding: 0.5rem 1rem; - vertical-align: top; - - &:hover { - border-bottom-color: var(--scm-hover-color); - color: var(--scm-hover-color); - } - - &.is-active { - border-bottom-color: var(--scm-info-color); - color: var(--scm-info-color); - } - - &:focus-visible { - background-color: var(--scm-column-selection); - } -`; - -const ScrollableUl = styled.ul` - max-height: 15.65rem; - width: 18.5rem; - overflow-x: hidden; - overflow-y: scroll; -`; - -const SizedDiv = styled.div` - width: 18.5rem; -`; - -const SmallButton = styled(Button)` - height: 1.875rem; -`; - -type BranchTabContentProps = { - elements: Branch[]; - selection: CompareProps; - onSelectEntry: CompareFunction; -}; - -const EmptyResultNotification: FC = () => { - const [t] = useTranslation("repos"); - - return {t("compare.selector.emptyResult")}; -}; - -const BranchTabContent: FC = ({ elements, selection, onSelectEntry }) => { - if (elements.length === 0) { - return ; - } - - return ( - <> - {elements.map(branch => { - return ( - onSelectEntry("b", branch.name)} - key={branch.name} - > - {branch.name} - - - ); - })} - - ); -}; - -type TagTabContentProps = { - elements: Tag[]; - selection: CompareProps; - onSelectEntry: CompareFunction; -}; - -const TagTabContent: FC = ({ elements, selection, onSelectEntry }) => { - if (elements.length === 0) { - return ; - } - - return ( - <> - {elements.map(tag => ( - onSelectEntry("t", tag.name)} - key={tag.name} - > - {tag.name} - - ))} - - ); -}; - -type RevisionTabContentProps = { - selected: CompareProps; - onSelect: CompareFunction; -}; - -const RevisionTabContent: FC = ({ selected, onSelect }) => { - const [t] = useTranslation("repos"); - const defaultValue = selected.type === "r" ? selected.name : ""; - const [revision, setRevision] = useState(defaultValue); - - const handleKeyPress = (event: KeyboardEvent) => { - if (event.key === "Enter") { - event.preventDefault(); - handleSubmit(); - } - }; - - const handleSubmit = () => { - if (revision) { - onSelect("r", revision); - } - }; - - return ( - -
    -
    - setRevision(e.target.value.trim())} - onKeyPress={handleKeyPress} - value={revision.trim()} - /> -
    -
    - - {t("compare.selector.revision.submit")} - -
    -
    -
    - ); -}; - -const ScrollableList: FC<{ selectedTab: CompareTypes } & Props> = ({ - selectedTab, - onSelect, - selected, - repository, - filter -}) => { - const { isLoading: branchesIsLoading, error: branchesError, data: branchesData } = useBranches(repository); - const branches: Branch[] = (branchesData?._embedded?.branches as Branch[]) || []; - const { isLoading: tagsIsLoading, error: tagsError, data: tagsData } = useTags(repository); - const tags: Tag[] = (tagsData?._embedded?.tags as Tag[]) || []; - const [selection, setSelection] = useState(selected); - - const onSelectEntry = (type: CompareTypes, name: string) => { - setSelection({ type, name }); - onSelect(type, name); - }; - - if (branchesIsLoading || tagsIsLoading) { - return ; - } - if (branchesError || tagsError) { - return ; - } - - if (selectedTab !== "r") { - return ( - - {selectedTab === "b" && ( - branch.name.includes(filter))} - selection={selection} - onSelectEntry={onSelectEntry} - /> - )} - {selectedTab === "t" && ( - tag.name.includes(filter))} - selection={selection} - onSelectEntry={onSelectEntry} - /> - )} - - ); - } - return null; -}; - -const CompareSelectorList: FC = ({ onSelect, selected, repository, filter }) => { - const [t] = useTranslation("repos"); - const [selectedTab, setSelectedTab] = useState(selected.type); - const tabs: CompareTypes[] = ["b", "t", "r"]; - - return ( - <> -
    -
      - {tabs.map(tab => { - return ( -
    • - setSelectedTab(tab)} - > - {t("compare.selector.tabs." + tab)} - -
    • - ); - })} -
    -
    - - {selectedTab === "r" && } - - ); -}; - -export default CompareSelectorList; diff --git a/scm-ui/ui-webapp/src/repos/compare/RevisionTab.tsx b/scm-ui/ui-webapp/src/repos/compare/RevisionTab.tsx new file mode 100644 index 0000000000..18784e14cf --- /dev/null +++ b/scm-ui/ui-webapp/src/repos/compare/RevisionTab.tsx @@ -0,0 +1,80 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { CompareFunction, CompareProps } from "./CompareSelectBar"; +import React, { FC } from "react"; +import { useTranslation } from "react-i18next"; +import styled from "styled-components"; +import { Button } from "@scm-manager/ui-buttons"; +import { Input } from "@scm-manager/ui-forms"; +import { SubmitHandler, useForm } from "react-hook-form"; + +type Props = { + selected: CompareProps; + onSelect: CompareFunction; +}; + +type FormProps = { + revision: string; +}; + +const SizedForm = styled.form` + width: 18.5rem; +`; + +const SmallButton = styled(Button)` + height: 1.875rem; +`; + +const RevisionTab: FC = ({ selected, onSelect }) => { + const [t] = useTranslation("repos"); + const defaultValue = selected.type === "r" ? selected.name : ""; + const { register, handleSubmit, watch } = useForm({ + defaultValues: { + revision: defaultValue, + }, + }); + const onSubmit: SubmitHandler = (data) => onSelect("r", data.revision); + + return ( + +
    +
    + +
    +
    + + {t("compare.selector.revision.submit")} + +
    +
    +
    + ); +}; + +export default RevisionTab; diff --git a/scm-ui/ui-webapp/src/repos/compare/TagTab.tsx b/scm-ui/ui-webapp/src/repos/compare/TagTab.tsx new file mode 100644 index 0000000000..09dbeacff3 --- /dev/null +++ b/scm-ui/ui-webapp/src/repos/compare/TagTab.tsx @@ -0,0 +1,93 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { FC, useState } from "react"; +import CompareSelectorListEntry from "./CompareSelectorListEntry"; +import { Repository, Tag } from "@scm-manager/ui-types"; +import { CompareFunction, CompareProps, CompareTypes } from "./CompareSelectBar"; +import { useTags } from "@scm-manager/ui-api"; +import { ErrorNotification, Loading, Notification } from "@scm-manager/ui-components"; +import styled from "styled-components"; +import { useTranslation } from "react-i18next"; + +type Props = { + onSelect: CompareFunction; + selected: CompareProps; + repository: Repository; + filter: string; +}; + +const FixedWidthNotification = styled(Notification)` + width: 18.5rem; + margin-top: 0.5rem; +`; + +const ScrollableUl = styled.ul` + max-height: 15.65rem; + width: 18.5rem; + overflow-x: hidden; + overflow-y: auto; +`; + +const TagTab: FC = ({ onSelect, selected, repository, filter }) => { + const [t] = useTranslation("repos"); + const { isLoading: tagsIsLoading, error: tagsError, data: tagsData } = useTags(repository); + const tags: Tag[] = (tagsData?._embedded?.tags as Tag[]) || []; + + const [selection, setSelection] = useState(selected); + + const onSelectEntry = (type: CompareTypes, name: string) => { + setSelection({ type, name }); + onSelect(type, name); + }; + + if (tagsIsLoading) { + return ; + } + if (tagsError) { + return ; + } + + const elements = tags.filter((tag) => tag.name.includes(filter)); + + if (elements.length === 0) { + return {t("compare.selector.emptyResult")}; + } + + return ( + + {elements.map((tag) => ( + onSelectEntry("t", tag.name)} + key={tag.name} + > + {tag.name} + + ))} + + ); +}; + +export default TagTab; diff --git a/yarn.lock b/yarn.lock index b3894cabdf..51d79b4198 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2929,6 +2929,21 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-compose-refs" "1.0.1" +"@radix-ui/react-tabs@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz#993608eec55a5d1deddd446fa9978d2bc1053da2" + integrity sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-direction" "1.0.1" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-roving-focus" "1.0.4" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-tooltip@1.0.2": version "1.0.2" resolved "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.2.tgz" @@ -8107,10 +8122,9 @@ bulma-popover@^1.0.0: resolved "https://registry.npmjs.org/bulma-popover/-/bulma-popover-1.1.1.tgz" integrity sha512-QvYJq04usgiCvTwnEZr/xtStkKRTToO/tc7JVcdtRBIBBWNaUu4tfHAOFXDMav8NFIvN8JOiQEtHO1dHNvVr1Q== -bulma@^0.9.3: +"bulma@https://github.com/scm-manager/bulma#bbe5bdb66fce67e2513ab8caefb1eac5504e1402": version "0.9.4" - resolved "https://registry.npmjs.org/bulma/-/bulma-0.9.4.tgz" - integrity sha512-86FlT5+1GrsgKbPLRRY7cGDg8fsJiP/jzTqXXVqiUZZ2aZT8uemEOHlU1CDU+TxklPEZ11HZNNWclRBBecP4CQ== + resolved "https://github.com/scm-manager/bulma#bbe5bdb66fce67e2513ab8caefb1eac5504e1402" bundle-require@^3.0.2: version "3.0.4" @@ -18132,7 +18146,7 @@ react-transition-group@^2.2.1: react@17, react@17.0.2, react@^16.8.3, react@^17.0.1: version "17.0.2" - resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== dependencies: loose-envify "^1.1.0"