Skip to content

Commit

Permalink
Feature: Add tabs navigation inside landing page (#808)
Browse files Browse the repository at this point in the history
* Add tabs component and logic

* Update constants

* Use tabs in RWS panel

* Update styles

* test: update and add test cases

* Update e2e

* Update style and text

* font size changes, and tab styles changes
  • Loading branch information
mayan-000 authored Aug 16, 2024
1 parent d05ce76 commit 34eb5d0
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 28 deletions.
2 changes: 2 additions & 0 deletions packages/design-system/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
97 changes: 97 additions & 0 deletions packages/design-system/src/components/tabs/index.tsx
Original file line number Diff line number Diff line change
@@ -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<string, any>;
};
}>;
}

const Tabs = ({ items }: TabsProps) => {
const [activeTab, setActiveTab] = useState(0);

const ActiveTabContent = items?.[activeTab].content?.Element;

const handleKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLButtonElement>) => {
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 (
<div className="max-w-2xl h-fit px-4">
<div className="flex gap-10 border-b border-gray-300 dark:border-quartz">
{items.map((item, index) => (
<button
key={index}
onClick={() => setActiveTab(index)}
onKeyDown={handleKeyDown}
autoFocus={index === 0}
className={classNames(
'pb-1.5 px-3 border-b-2 hover:opacity-80 outline-none text-sm',
{
'border-bright-navy-blue dark:border-jordy-blue text-bright-navy-blue dark:text-jordy-blue':
index === activeTab,
},
{
'border-transparent text-raisin-black dark:text-bright-gray':
index !== activeTab,
}
)}
>
{item.title}
</button>
))}
</div>
<div className="pt-4">
{ActiveTabContent && (
<ActiveTabContent {...items?.[activeTab].content?.props} />
)}
</div>
</div>
);
};

export default Tabs;
71 changes: 71 additions & 0 deletions packages/design-system/src/components/tabs/tests/index.tsx
Original file line number Diff line number Diff line change
@@ -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: () => <div>content1</div>,
},
},
{
title: 'title2',
content: {
Element: () => <div>content2</div>,
},
},
],
};

it('should render', () => {
render(<Tabs {...props} />);

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');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand All @@ -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 (
<div data-testid="related-website-sets-content" className="h-full w-full">
<LandingPage
title={I18n.getMessage('rws')}
psInfoKey={PSInfoKey.RelatedWebsiteSets}
extraClasses="max-w-2xl h-fit"
>
<Insights />
<RWSJsonGenerator />
</LandingPage>
<div className="p-4">
<div className="flex gap-2 text-2xl font-bold items-baseline text-raisin-black dark:text-bright-gray">
<h1 className="text-left">{I18n.getMessage('rws')}</h1>
</div>
</div>
<Tabs items={tabItems} />
<div className="mt-8 border-t border-gray-300 dark:border-quartz">
<QuickLinksList />
</div>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,6 @@ const Insights = () => {
</div>
) : (
<div className="space-y-3">
<h3 className="text-xl font-semibold">
{I18n.getMessage('membership')}
</h3>
{insightsData?.isURLInRWS ? (
<>
<p className="text-lg font-medium">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const RWSInput = ({
}: RWSInputProps) => {
return (
<div className="flex flex-col gap">
<label className="text-xs">{inputLabel}</label>
<label className="text-sm">{inputLabel}</label>
<input
type="text"
className={classNames(
Expand All @@ -57,7 +57,7 @@ const RWSInput = ({
onChange={inputChangeHandler}
/>
{error && formValidationFailed && (
<span className="text-red-500 text-xs mt-2">{error}</span>
<span className="text-red-500 text-sm mt-2">{error}</span>
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,10 @@ const RWSJsonGenerator = () => {

return (
<>
<div className="overflow-auto py-6">
<div className="overflow-auto">
<div className="text-raisin-black dark:text-bright-gray w-full min-w-[33rem]">
<h1 className="text-lg font-semibold">
{I18n.getMessage('rwsJsonGenerator')}
</h1>
<p
className="text-xs py-3"
className="text-sm"
dangerouslySetInnerHTML={{
__html: I18n.getMessage('rwsJsonGeneratorNote', [
`<a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,6 @@ describe('RWSJsonGenerator', () => {
});
});

it('should render form', () => {
const screen = render(<RWSJsonGenerator open={true} setOpen={noop} />);

expect(
screen.getByText('Related Website Sets JSON Generator')
).toBeInTheDocument();
});

it('should interact with contact email and primary domain', async () => {
const screen = render(<RWSJsonGenerator open={true} setOpen={noop} />);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions tailwind.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit 34eb5d0

Please sign in to comment.