diff --git a/packages/design-system/src/components/index.ts b/packages/design-system/src/components/index.ts index e9b5ddf8e..063c2a967 100644 --- a/packages/design-system/src/components/index.ts +++ b/packages/design-system/src/components/index.ts @@ -52,3 +52,5 @@ export { default as MenuBar } from './menuBar'; export * from './menuBar'; export { default as useFiltersMapping } from './cookiesLanding/useFiltersMapping'; export { default as useGlobalFiltering } from './cookiesLanding/useGlobalFiltering'; +export { default as Tabs } from './tabs'; +export { default as QuickLinksList } from './landingPage/quickLinksList'; diff --git a/packages/design-system/src/components/tabs/index.tsx b/packages/design-system/src/components/tabs/index.tsx new file mode 100644 index 000000000..5f4091346 --- /dev/null +++ b/packages/design-system/src/components/tabs/index.tsx @@ -0,0 +1,97 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import classNames from 'classnames'; +import React, { useCallback, useState } from 'react'; + +interface TabsProps { + items: Array<{ + title: string; + content: { + Element: (props: any) => React.JSX.Element; + props?: Record; + }; + }>; +} + +const Tabs = ({ items }: TabsProps) => { + const [activeTab, setActiveTab] = useState(0); + + const ActiveTabContent = items?.[activeTab].content?.Element; + + const handleKeyDown = useCallback( + (event: React.KeyboardEvent) => { + event.preventDefault(); + + if (event.key === 'Tab') { + const nextIndex = activeTab + 1; + if (nextIndex < items.length) { + setActiveTab(nextIndex); + } else { + setActiveTab(0); + } + } + + if (event.shiftKey && event.key === 'Tab') { + const previousIndex = activeTab - 1; + if (previousIndex >= 0) { + setActiveTab(previousIndex); + } else { + setActiveTab(items.length - 1); + } + } + }, + [activeTab, items.length] + ); + + return ( +
+
+ {items.map((item, index) => ( + + ))} +
+
+ {ActiveTabContent && ( + + )} +
+
+ ); +}; + +export default Tabs; diff --git a/packages/design-system/src/components/tabs/tests/index.tsx b/packages/design-system/src/components/tabs/tests/index.tsx new file mode 100644 index 000000000..2f4e9927c --- /dev/null +++ b/packages/design-system/src/components/tabs/tests/index.tsx @@ -0,0 +1,71 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; + +/** + * Internal dependencies + */ +import Tabs from '..'; + +describe('Tabs', () => { + const props = { + items: [ + { + title: 'title1', + content: { + Element: () =>
content1
, + }, + }, + { + title: 'title2', + content: { + Element: () =>
content2
, + }, + }, + ], + }; + + it('should render', () => { + render(); + + expect(screen.getByText('title1')).toBeInTheDocument(); + expect(screen.getByText('title1')).toHaveClass('border-b-2'); + + fireEvent.click(screen.getByText('title2')); + + expect(screen.getByText('content2')).toBeInTheDocument(); + expect(screen.getByText('title2')).toHaveClass('border-b-2'); + + fireEvent.keyDown(screen.getByText('title2'), { key: 'Tab' }); + + expect(screen.getByText('content1')).toBeInTheDocument(); + expect(screen.getByText('title1')).toHaveClass('border-b-2'); + + fireEvent.keyDown(screen.getByText('title1'), { + key: 'Tab', + shiftKey: true, + }); + + expect(screen.getByText('content2')).toBeInTheDocument(); + expect(screen.getByText('title2')).toHaveClass('border-b-2'); + }); +}); diff --git a/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/index.tsx b/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/index.tsx index cb4bf84d5..ce496a79e 100644 --- a/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/index.tsx +++ b/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/index.tsx @@ -16,8 +16,13 @@ /** * External dependencies. */ -import React from 'react'; -import { LandingPage, PSInfoKey } from '@google-psat/design-system'; +import React, { useMemo } from 'react'; +import { + InfoCard, + PSInfoKey, + QuickLinksList, + Tabs, +} from '@google-psat/design-system'; import { I18n } from '@google-psat/i18n'; /** @@ -27,16 +32,44 @@ import RWSJsonGenerator from './jsonGenerator'; import Insights from './insights'; const RelatedWebsiteSets = () => { + const tabItems = useMemo( + () => [ + { + title: 'Overview', + content: { + Element: InfoCard, + props: { + infoKey: PSInfoKey.RelatedWebsiteSets, + }, + }, + }, + { + title: 'Membership', + content: { + Element: Insights, + }, + }, + { + title: 'JSON Generator', + content: { + Element: RWSJsonGenerator, + }, + }, + ], + [] + ); + return (
- - - - +
+
+

{I18n.getMessage('rws')}

+
+
+ +
+ +
); }; diff --git a/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/insights/index.tsx b/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/insights/index.tsx index d063eec38..5cbfb1389 100644 --- a/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/insights/index.tsx +++ b/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/insights/index.tsx @@ -88,9 +88,6 @@ const Insights = () => { ) : (
-

- {I18n.getMessage('membership')} -

{insightsData?.isURLInRWS ? ( <>

diff --git a/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/jsonGenerator/components/input/index.tsx b/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/jsonGenerator/components/input/index.tsx index 03960b2ec..88415b29f 100644 --- a/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/jsonGenerator/components/input/index.tsx +++ b/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/jsonGenerator/components/input/index.tsx @@ -39,7 +39,7 @@ const RWSInput = ({ }: RWSInputProps) => { return (

- + {error && formValidationFailed && ( - {error} + {error} )}
); diff --git a/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/jsonGenerator/index.tsx b/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/jsonGenerator/index.tsx index 7f7e74219..cc14d461b 100644 --- a/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/jsonGenerator/index.tsx +++ b/packages/extension/src/view/devtools/components/siteBoundaries/relatedWebsiteSets/jsonGenerator/index.tsx @@ -64,13 +64,10 @@ const RWSJsonGenerator = () => { return ( <> -
+
-

- {I18n.getMessage('rwsJsonGenerator')} -

{ }); }); - it('should render form', () => { - const screen = render(); - - expect( - screen.getByText('Related Website Sets JSON Generator') - ).toBeInTheDocument(); - }); - it('should interact with contact email and primary domain', async () => { const screen = render(); diff --git a/packages/extension/src/view/devtools/e2e-tests/rwsMembership/index.ts b/packages/extension/src/view/devtools/e2e-tests/rwsMembership/index.ts index 24c389a4b..b0fcd34f4 100644 --- a/packages/extension/src/view/devtools/e2e-tests/rwsMembership/index.ts +++ b/packages/extension/src/view/devtools/e2e-tests/rwsMembership/index.ts @@ -69,6 +69,7 @@ describe('RWS membership', () => { // Check if RWS is present and click on it. await interaction.clickMatchingElement(frame, 'p', 'Related Website Sets'); + await interaction.clickMatchingElement(frame, 'button', 'Membership'); await interaction.delay(6000); const rwsmembershiptext = await interaction.getInnerText( diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 29269e5a2..4adaa675e 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -221,6 +221,7 @@ module.exports = { ...colors, 'american-silver': '#CBCDD1', 'royal-blue': '#3871E0', + 'jordy-blue': '#8AB7F8', 'medium-persian-blue': '#0E639C', 'bright-navy-blue': '#1A73E8', 'hex-gray': '#E0E0E0',