diff --git a/src/licenses/LicenseDialog.test.tsx b/src/licenses/LicenseDialog.test.tsx index 2af73815..aa135830 100644 --- a/src/licenses/LicenseDialog.test.tsx +++ b/src/licenses/LicenseDialog.test.tsx @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 The Pybricks Authors +import { Classes } from '@blueprintjs/core'; import { cleanup } from '@testing-library/react'; import React from 'react'; import { testRender } from '../../test'; @@ -41,13 +42,15 @@ describe('LicenseDialog', () => { ); // have to wait for async fetch - const button = await dialog.findByText('super-duper', { selector: 'button *' }); + const treeNode = await dialog.findByText('super-duper', { + selector: `.${Classes.TREE_NODE} *`, + }); // when the dialog is first show, no license is selected expect(dialog.queryByText('Joe Somebody')).toBeNull(); - // then when you click on a license button, the license is shown - button.click(); + // then when you click on a license name, the license is shown + treeNode.click(); expect(dialog.getByText('Joe Somebody')).toBeDefined(); }); }); diff --git a/src/licenses/LicenseDialog.tsx b/src/licenses/LicenseDialog.tsx index dc81e61a..7e51833c 100644 --- a/src/licenses/LicenseDialog.tsx +++ b/src/licenses/LicenseDialog.tsx @@ -4,17 +4,18 @@ // The license dialog import { - Button, - ButtonGroup, Callout, Card, Classes, Dialog, NonIdealState, Spinner, + Tree, + TreeEventHandler, + TreeNodeInfo, } from '@blueprintjs/core'; import { useI18n } from '@shopify/react-i18n'; -import React, { useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { useFetch } from 'usehooks-ts'; import { appName } from '../app/constants'; import { LicenseStringId } from './i18n'; @@ -33,7 +34,7 @@ interface LicenseInfo { type LicenseList = ReadonlyArray; type LicenseListPanelProps = { - onItemClick(info: LicenseInfo): void; + onItemClick(info?: LicenseInfo): void; }; const LicenseListPanel: React.VoidFunctionComponent = ({ @@ -41,10 +42,32 @@ const LicenseListPanel: React.VoidFunctionComponent = ({ }) => { const [i18n] = useI18n({ id: 'license', translations: { en }, fallback: en }); const { data, error } = useFetch('static/oss-licenses.json'); + const [selectedNode, setSelectedNode] = useState(undefined); + + const contents = useMemo(() => { + if (!data) { + return undefined; + } + + return data.map>((info, i) => ({ + id: i, + label: info.name, + isSelected: info.name === selectedNode, + nodeData: info, + })); + }, [data, selectedNode]); + + const handleNodeClick = useCallback>( + (e) => { + setSelectedNode(e.nodeData?.name); + onItemClick(e.nodeData); + }, + [setSelectedNode, onItemClick], + ); return (
- {!data ? ( + {contents === undefined ? ( {error ? ( i18n.translate(LicenseStringId.ErrorFetchFailed) @@ -53,21 +76,15 @@ const LicenseListPanel: React.VoidFunctionComponent = ({ )} ) : ( - - {data.map((info, i) => ( - - ))} - + )}
); }; type LicenseInfoPanelProps = { - /** The license info to show or null if no license info is selected. */ - licenseInfo: LicenseInfo | null; + /** The license info to show or undefined if no license info is selected. */ + licenseInfo: LicenseInfo | undefined; }; const LicenseInfoPanel = React.forwardRef( @@ -76,7 +93,7 @@ const LicenseInfoPanel = React.forwardRef return (
- {licenseInfo == null ? ( + {licenseInfo === undefined ? ( {i18n.translate(LicenseStringId.SelectPackageHelp)} @@ -128,7 +145,7 @@ const LicenseDialog: React.VoidFunctionComponent = ({ isOpen, onClose, }) => { - const [licenseInfo, setLicenseInfo] = useState(null); + const [licenseInfo, setLicenseInfo] = useState(undefined); const infoDiv = React.useRef(null); const [i18n] = useI18n({ id: 'license', translations: { en }, fallback: en });