Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: migrate leftover components to typescript #2825

Merged
merged 11 commits into from
Apr 7, 2024
197 changes: 197 additions & 0 deletions components/CaseTOC.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { useMemo, useState } from 'react';
import { twMerge } from 'tailwind-merge';

import { useHeadingsObserver } from './helpers/useHeadingsObserver';
import ArrowRight from './icons/ArrowRight';

interface TocItem {
lvl: number;
content: string;
slug: string;
children?: TocItem[];
}

interface TOCItemProps {
item: TocItem;
index: number;
currSelected: string;
closeMenu: () => void;
}

interface CaseTOCProps {
className: string;
cssBreakingPoint?: 'xl' | 'lg';
toc: any[];
}

/**
* @description Checks if the item is active.
*
* @param {TocItem} item - The TOC item to check.
* @param {string} currSelected - The currently selected TOC item.
* @returns {boolean} - True if the item is active, otherwise false.
*/
const checkIfActive = (item: TocItem, currSelected: string): boolean => {
return item.slug === currSelected || item.children?.some((child) => checkIfActive(child, currSelected)) || false;
};

/**
* @description Converts content to TOC items.
*
* @param {any[]} content - The content to convert to TOC items.
* @param {number} level - The level of the TOC item.
* @returns {TocItem[]} - The array of TOC items.
*/
const convertContentToTocItems = (content: any[], level: number = 1): TocItem[] => {
const tocItems = [];

for (const section of content) {
const item = {
lvl: level,
content: section.title,
slug: section.title
.replace(/<|>|"|\\|\/|=/gi, '')
.replace(/\s/gi, '-')
.toLowerCase()
};

if (section.children && section.children.length > 0) {
const children = convertContentToTocItems(section.children, level + 1);

(item as TocItem).children = children;
}

tocItems.push(item);
}

return tocItems;
};

/**
* @description Component representing an item in the table of contents (TOC).
*
* @param {TOCItemProps} props - The props for TOCItem.
* @param {TocItem} props.item - The TOC item.
* @param {number} props.index - The index of the TOC item.
* @param {string} props.currSelected - The currently selected TOC item.
* @param {Function} props.closeMenu - A function to close the menu.
*/
function TOCItem({ item, index, currSelected, closeMenu }: TOCItemProps) {
const [open, setOpen] = useState(false);
const handleClick = () => {
akshatnema marked this conversation as resolved.
Show resolved Hide resolved
closeMenu();
setOpen(false);
};
const active = useMemo(() => checkIfActive(item, currSelected), [item, currSelected]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Define this above, in next line of open state.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!


return (
<>
<nav className='relative block max-w-max'>
<a
className={`font-normal mb-1 flex items-center font-sans text-sm text-gray-900 antialiased transition duration-100 ease-in-out hover:underline ${
active && 'font-bold text-primary-500'
}`}
href={`#${item.slug}`}
key={index}
style={{ marginLeft: `${(item.lvl - 1) * 16}px` }}
onClick={handleClick}
>
{item.content}
</a>
{item.children && item.children.length > 0 && (
<span onClick={() => setOpen(!open)} className='absolute -right-6 top-0 cursor-pointer '>
<ArrowRight
className={`${open ? 'rotate-90' : '0'} h-5 text-gray-500 transition duration-200 ease-in-out`}
/>
</span>
)}
</nav>
{item.children && item.children.length > 0 && (
<ul
className={`relative left-0 ${
open ? 'max-h-[1000px]' : 'max-h-[0.01px]'
} overflow-hidden transition-all duration-300 ease-in-out`}
>
{item.children.map((child_item, child_index) => (
<TOCItem
item={child_item}
index={child_index}
key={index}
closeMenu={closeMenu}
currSelected={currSelected}
/>
))}
</ul>
)}
</>
);
}

/**
* @description Component representing a table of contents (TOC) for a case.
*
* @param {CaseTOCProps} props - The props for CaseTOC.
* @param {string} props.className - The CSS class name for the component.
* @param {("xl"|"lg")} [props.cssBreakingPoint="xl"] - The CSS breaking point for responsiveness.
* @param {any[]} props.toc - The table of contents data.
*/
export default function CaseTOC({ className, cssBreakingPoint = 'xl', toc }: CaseTOCProps) {
const { currActive: selected } = useHeadingsObserver();
const [open, setOpen] = useState(false);
const tocItems = useMemo(() => convertContentToTocItems(toc), [toc]);

if (!toc || !toc.length) return null;

return (
<div
className={twMerge(
`${className} ${tocItems.length ? '' : 'hidden'} ${
cssBreakingPoint === 'xl' ? 'xl:block' : 'lg:block'
} md:top-24 md:max-h-(screen-14) z-20`
)}
>
<div
className={`flex cursor-pointer ${tocItems.length ? '' : 'hidden'} ${
cssBreakingPoint === 'xl' ? 'xl:cursor-auto' : 'lg:cursor-auto'
} xl:mt-2`}
>
<h5
className={twMerge(
`${
open && 'mb-4'
} flex-1 text-primary-500 font-medium uppercase tracking-wide text-sm font-sans antialiased ${
cssBreakingPoint === 'xl'
? 'xl:mb-4 xl:text-xs xl:text-gray-900 xl:font-bold'
: 'lg:mb-4 lg:text-xs lg:text-gray-900 lg:font-bold'
}`
)}
>
On this page
</h5>
<div
className={`text-underline p4 text-center ${cssBreakingPoint === 'xl' ? 'xl:hidden' : 'lg:hidden'}`}
onClick={() => setOpen(!open)}
>
<ArrowRight
className={`${
open ? '-rotate-90' : 'rotate-90'
} -mt-0.5 h-6 text-primary-500 transition duration-200 ease-in-out`}
/>
</div>
</div>
<div className={`${!open && 'hidden'} ${cssBreakingPoint === 'xl' ? 'xl:block' : 'lg:block'}`}>
<ul className='mt-2'>
{tocItems.map((item, index) => (
<TOCItem
item={item}
index={index}
key={index}
closeMenu={() => setOpen(false)}
currSelected={selected || ''}
/>
))}
</ul>
</div>
</div>
);
}
99 changes: 99 additions & 0 deletions components/GeneratorInstallation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useState } from 'react';

import { ParagraphTypeStyle } from '@/types/typography/Paragraph';

import generatorflagList from '../config/generator-flags.json';
import generatorTemplates from '../config/generator-templates.json';
import CodeBlock from './editor/CodeBlock';
import Select from './form/Select';
import Paragraph from './typography/Paragraph';

interface GeneratorFlagData {
flag: string;
specPath: string;
}

interface GeneratorFlags {
[key: string]: GeneratorFlagData;
}

/**
* @description This component displays generator installation options.
*/
export default function GeneratorInstallation() {
const [template, setTemplate] = useState<string>('@asyncapi/html-template');
// By default we will have output folder flag so its set here.
const [params, setParams] = useState<string>('-o example');
const [specPath, setSpecPath] = useState<string>('https://bit.ly/asyncapi');

const generatorflags = generatorflagList as GeneratorFlags;

/**
* @description Handles the change event when selecting a generator template.
* @param {string} templateName - The name of the selected template.
*/
function onChangeTemplate(templateName: string) {
setTemplate(templateName);
if (templateName && generatorflags[templateName]) {
const templateBasedJSON = generatorflags[templateName];

// options are generated from generator-templates.json
// and flags are fetched from generator-flags.json,
// so it is mandatory to have check in case if any misses the option in future

if (templateBasedJSON) {
setParams(templateBasedJSON.flag);
setSpecPath(templateBasedJSON.specPath);
}
}
}

/**
* @description Generates the npm install command.
* @returns {string} The npm install command.
*/
function getNpmCode(): string {
return `npm install -g @asyncapi/cli
asyncapi generate fromTemplate ${specPath} ${template} ${params}`;
}

/**
* Generates the Docker command.
* @returns {string} The Docker command.
*/
function getDockerCode(): string {
return `docker run --rm -it -v \${PWD}/example:/app/example -v \${PWD}/output:/app/output \\
asyncapi/cli generate fromTemplate ${specPath} ${template} ${params}`;
}

return (
<div className='relative mx-auto mt-8 max-w-full'>
<div className='mb-4'>
<Paragraph typeStyle={ParagraphTypeStyle.md} className='mr-4 inline'>
Select a Generator template:
</Paragraph>
<Select
options={generatorTemplates}
selected={template}
onChange={onChangeTemplate}
className='shadow-outline-blue'
/>
</div>
<CodeBlock
language='generator-cli'
textSizeClassName='text-sm'
className='shadow-lg'
codeBlocks={[
{
language: 'npm',
code: getNpmCode()
},
{
language: 'Docker',
code: getDockerCode()
}
]}
/>
</div>
);
}
87 changes: 87 additions & 0 deletions components/Hero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading';
import { ParagraphTypeStyle } from '@/types/typography/Paragraph';

import { useTranslation } from '../utils/i18n';
import AlgoliaSearch, { SearchButton } from './AlgoliaSearch'; // Import AlgoliaSearch component
import Button from './buttons/Button';
import AnnouncementHero from './campaigns/AnnouncementHero';
import DemoAnimation from './DemoAnimation';
import Features from './features';
import ArrowRight from './icons/ArrowRight';
import IconLoupe from './icons/Loupe';
import Heading from './typography/Heading';
import Paragraph from './typography/Paragraph';

interface HeroProps {
className?: string;
}

/**
* @description This component displays the hero section on the Home page.
*
* @param {HeroProps} props - The props for Hero Component.
* @param {string} props.className - Additional CSS classes for styling.
*/
export default function Hero({ className = '' }: HeroProps) {
const { t } = useTranslation('landing-page');

return (
<>
<AnnouncementHero className='my-4' />
<header className={`mt-12 px-2 ${className}`}>
<div className='text-center'>
<Heading level={HeadingLevel.h1} typeStyle={HeadingTypeStyle.xl} className='mb-4'>
{t('main.header')} <span className='block md:-mt-4'> {t('main.subHeader')}</span>
</Heading>
<Heading
level={HeadingLevel.h2}
typeStyle={HeadingTypeStyle.bodyLg}
textColor='text-gray-700'
className='mx-auto mb-10 max-w-4xl'
>
{t('main.body_pretext')} <strong>{t('main.body_boldtext')}</strong>
{t('main.body_posttext')}
</Heading>
<div className='flex flex-col items-center justify-center gap-2 md:flex-row'>
<Button
className='block w-full md:w-auto'
text={t('main.docs_btn')}
href='/docs'
icon={<ArrowRight className='-mb-1 size-5' />}
data-testid='Hero-Button'
/>
{/* Wrap SearchButton with AlgoliaSearch component */}
<AlgoliaSearch>
<SearchButton className='flex w-full items-center space-x-3 rounded-md border border-secondary-500 bg-secondary-100 px-4 py-3 text-left text-secondary-500 shadow-md transition-all duration-500 ease-in-out hover:bg-secondary-500 hover:text-white md:w-auto'>
{({ actionKey }) => (
<>
<IconLoupe />
<span className='flex-auto'>{t('main.search_btn')}</span>
{actionKey && (
<kbd className='font-sans font-semibold'>
<abbr title={actionKey.key} className='no-underline'>
{actionKey.shortKey}
</abbr>{' '}
K
</kbd>
)}
</>
)}
</SearchButton>
</AlgoliaSearch>
</div>
<Paragraph typeStyle={ParagraphTypeStyle.sm} className='mt-4' textColor='text-gray-500'>
{t('main.slogan_text')}{' '}
<a className='underline' href='https://www.linuxfoundation.org/'>
{t('main.slogan_link')}
</a>
</Paragraph>
</div>
<div className='mt-8 md:mt-16'>
<DemoAnimation />
</div>
<Features />
</header>
</>
);
}
Loading
Loading