Skip to content

Commit

Permalink
Merge pull request #27 from Quiddlee/feat/add_docs_section
Browse files Browse the repository at this point in the history
Feat/add docs section
  • Loading branch information
Quiddlee authored Dec 27, 2023
2 parents 3585fc2 + 6fa6c5d commit 4a94030
Show file tree
Hide file tree
Showing 25 changed files with 8,707 additions and 4 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ module.exports = {
'react/require-default-props': 0,
'import/no-extraneous-dependencies': 0,
'react/function-component-definition': 0,
'no-underscore-dangle': 0,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'sort-imports': ['error', {ignoreCase: true, ignoreDeclarationSort: true}],
'import/order': [
Expand Down
19 changes: 19 additions & 0 deletions src/components/DocsComp/DocsComp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import useSchemaExplorer from './lib/hooks/useSchemaExplorer';
import DocsModal from './ui/DocsModal';
import DocsOverlay from './ui/DocsOverlay';

type PropsType = {
setIsDocsShown: React.Dispatch<React.SetStateAction<boolean>>;
isShown: boolean;
};

const DocsComp = ({ isShown, setIsDocsShown }: PropsType) => {
const schemaExplorer = useSchemaExplorer();
return (
<DocsOverlay isShown={isShown} setIsDocsShown={setIsDocsShown} explorer={schemaExplorer}>
<DocsModal setIsDocsShown={setIsDocsShown} explorer={schemaExplorer} />
</DocsOverlay>
);
};

export default DocsComp;
32 changes: 32 additions & 0 deletions src/components/DocsComp/lib/helpers/getTypeName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { SchemaTypeFieldOfType } from '@/shared/types';

function getTypeName(type: SchemaTypeFieldOfType) {
if (type?.name) return type?.name;

const modifiers: string[] = [];

if (type?.kind === 'NON_NULL') modifiers.push('!');
if (type?.kind === 'LIST') modifiers.push('[]');

function getOfTypeName(argOfType: SchemaTypeFieldOfType): number | string {
if (argOfType?.name) {
return modifiers.push(argOfType.name);
}
if (argOfType?.kind === 'NON_NULL') modifiers.push('!');
if (argOfType?.kind === 'LIST') modifiers.push('[]');

return getOfTypeName(argOfType?.ofType as SchemaTypeFieldOfType);
}

getOfTypeName(type?.ofType as SchemaTypeFieldOfType);

const output = modifiers.reduceRight((acc, curr) => {
if (curr === '!') return `${acc}!`;
if (curr === '[]') return `[${acc}]`;
return acc + curr;
});

return output;
}

export default getTypeName;
12 changes: 12 additions & 0 deletions src/components/DocsComp/lib/helpers/separateString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
function separateString(inputString: string) {
const regex = /([^a-zA-Z]*)([a-zA-Z]+)([^a-zA-Z]*$)/;
const matches = inputString.match(regex);

if (matches) {
const [, beforeLetters, letters, afterLetters] = matches;
return [beforeLetters, letters, afterLetters];
}
return ['', inputString, ''];
}

export default separateString;
30 changes: 30 additions & 0 deletions src/components/DocsComp/lib/hooks/useSchemaExplorer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useState } from 'react';

function useSchemaExplorer() {
const [typeNames, setTypeNames] = useState(['Docs']);

function current() {
return typeNames[typeNames.length - 1];
}
function prev() {
return typeNames[typeNames.length - 2];
}
function next(elem: string) {
setTypeNames((prevEl) => [...prevEl, elem]);
}
function back() {
if (typeNames.length > 1) {
setTypeNames((prevEl) => prevEl.filter((_, i) => i < prevEl.length - 1));
}
}
function isDocs() {
return current() === 'Docs';
}
function setInitState() {
setTypeNames(['Docs']);
}

return { current, next, prev, isDocs, back, setInitState };
}

export default useSchemaExplorer;
16 changes: 16 additions & 0 deletions src/components/DocsComp/ui/BackDocsBtn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FC } from 'react';

import Icon from '@/shared/ui/Icon';

type PropsType = { onClick: () => void; title: string };

const BackDocsBtn: FC<PropsType> = ({ onClick, title }: PropsType) => {
return (
<button type="button" onClick={onClick} className="flex items-center gap-1 rounded-full p-2 hover:bg-slate-500/30">
<Icon>arrow_back_ios</Icon>
<span className="text-2xl">{title}</span>
</button>
);
};

export default BackDocsBtn;
16 changes: 16 additions & 0 deletions src/components/DocsComp/ui/CloseDocsBtn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FC } from 'react';

import Icon from '@/shared/ui/Icon';
import IconButton from '@/shared/ui/IconButton';

type PropsType = { onClick: () => void; className: string };

const CloseDocsBtn: FC<PropsType> = ({ onClick, className }) => {
return (
<IconButton onClick={onClick} className={className}>
<Icon>close</Icon>
</IconButton>
);
};

export default CloseDocsBtn;
40 changes: 40 additions & 0 deletions src/components/DocsComp/ui/DocsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// AFTER LOGIC FOR SAVING AND FETCHING ENDPOINT SHEMA WILL BE ADDED - MUST REMOVE SCHEMA IMPORT AND REPLACE IT FOR DOWNLOADED SCHEMA IN FURTHER CODE

import { Dispatch, SetStateAction } from 'react';

import { swapiSchema } from '@/shared/constants/schemaData';
import { DocsExplorerType, SchemaTypeObj } from '@/shared/types';
import CloseDocsBtn from '@components/DocsComp/ui/CloseDocsBtn';

import DocsRootComp from './DocsRootComp';
import DocsTypeComp from './DocsTypeComp';

type PropsType = {
setIsDocsShown: Dispatch<SetStateAction<boolean>>;
explorer: DocsExplorerType;
};

const DocsModal = ({ setIsDocsShown, explorer }: PropsType) => {
const content = explorer.isDocs() ? (
<DocsRootComp types={swapiSchema.data.__schema.types as SchemaTypeObj[]} explorer={explorer} />
) : (
<DocsTypeComp
explorer={explorer}
currType={swapiSchema.data.__schema.types.find((elem) => elem.name === explorer.current()) as SchemaTypeObj}
/>
);
return (
<section className="relative z-20 h-[100dvh] w-[420px] cursor-auto rounded-r-[28px] bg-surface p-3">
<CloseDocsBtn
onClick={() => {
setIsDocsShown((prev) => !prev);
explorer.setInitState();
}}
className="absolute right-[20px] top-[20px] z-20"
/>
{content}
</section>
);
};

export default DocsModal;
38 changes: 38 additions & 0 deletions src/components/DocsComp/ui/DocsOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
type PropsType = {
setIsDocsShown: React.Dispatch<React.SetStateAction<boolean>>;
isShown: boolean;
explorer: {
current: () => string;
next: (elem: string) => void;
prev: () => string;
isDocs: () => boolean;
back: () => void;
setInitState: () => void;
};
children: JSX.Element;
};

const DocsOverlay = ({ isShown, setIsDocsShown, explorer, children }: PropsType) => {
function closeHandler(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
if ((e.target as HTMLButtonElement).hasAttribute('data-overlay')) {
setIsDocsShown((prev) => !prev);
explorer.setInitState();
}
}
if (!isShown) {
return null;
}
return (
<button
data-testid="overlay"
data-overlay
type="button"
onClick={(e) => closeHandler(e)}
className="overlay absolute left-0 top-0 z-10 flex h-full w-full justify-start bg-black/60 "
>
{children}
</button>
);
};

export default DocsOverlay;
53 changes: 53 additions & 0 deletions src/components/DocsComp/ui/DocsRootComp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import useScrollbar from '@/shared/lib/hooks/useScrollbar';
import { DocsExplorerType, SchemaTypeObj } from '@/shared/types';

const DocsRootComp = ({ types, explorer }: { types: SchemaTypeObj[]; explorer: DocsExplorerType }) => {
function clinkHandler(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>, typeName: string) {
e.preventDefault();
explorer.next(typeName);
}
const allTypes = types
.filter((type) => type.name[0] !== '_' && type.name[1] !== '_')
.map((type, i) => {
if (i > 0) {
return (
<li key={type.name}>
<a
className="text-docs-link-text-color hover:underline"
href={type.name}
onClick={(e) => clinkHandler(e, type.name)}
>
{type.name}
</a>
</li>
);
}
return null;
});
const rootRef = useScrollbar<HTMLDivElement>();
return (
<div ref={rootRef} className="h-full">
<div className="rounded-[24px] bg-surface-container px-10 py-[56px] text-left text-on-surface sm:px-[56px]">
<h3 className="text-[57px] font-[500]">Docs</h3>
<p className="text-md text-left">A GraphQL schema provides a root type for each kind of operation.</p>
</div>
<div className="mt-[56px] p-10 text-left font-[500] sm:px-[56px]">
<h4 className="text-[28px]">Root types:</h4>
<p className="mt-4">
query:&nbsp;
<a
className="text-docs-link-text-color hover:underline"
href={types[0].name}
onClick={(e) => clinkHandler(e, types[0].name)}
>
{types[0].name}
</a>
</p>
<h4 className="mt-[56px] text-[28px]">All schema types:</h4>
<ul className="mt-4">{allTypes}</ul>
</div>
</div>
);
};

export default DocsRootComp;
122 changes: 122 additions & 0 deletions src/components/DocsComp/ui/DocsTypeComp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import useScrollbar from '@/shared/lib/hooks/useScrollbar';
import { DocsExplorerType, SchemaTypeObj } from '@/shared/types';

import BackDocsBtn from './BackDocsBtn';
import getTypeName from '../lib/helpers/getTypeName';
import separateString from '../lib/helpers/separateString';

const DocsTypeComp = ({ explorer, currType }: { explorer: DocsExplorerType; currType: SchemaTypeObj }) => {
function clinkHandler(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>, typeName: string) {
e.preventDefault();
explorer.next(typeName);
}

const fields = currType?.fields?.map((field) => {
const args = field.args.map((arg, i) => {
const argTypeName = getTypeName(arg.type);
const [before, link, after] = separateString(argTypeName);
const separation = field.args.length > 1;
const beforeSeparator = <br />;
const afterSeparator = i >= field.args.length - 1 ? <br /> : null;
return (
<>
{separation && beforeSeparator}
<span className={separation ? 'pl-3' : ''} key={argTypeName}>
<span className="text-tertiary">{arg.name}</span>:&nbsp;
{before}
<a className="text-docs-link-text-color hover:underline" href={link} onClick={(e) => clinkHandler(e, link)}>
{link}
</a>
{after}
</span>
{separation && afterSeparator}
</>
);
});
const returnType = getTypeName(field.type);
const [prevType, typeLink, afterType] = separateString(returnType);
return (
<li key={field.name}>
<span className="text-docs-field-text-color">{field.name}</span>
{args.length > 0 && '('}
{args}
{args.length > 0 && ')'}: {prevType}
<a
className="text-docs-link-text-color hover:underline"
href={typeLink}
onClick={(e) => clinkHandler(e, typeLink)}
>
{typeLink}
</a>
{afterType}
<br />
{field.description}
</li>
);
});
const inputFields = currType?.inputFields?.map((field) => {
return (
<li key={field.name}>
{field.name}:&nbsp;
<a
className="text-docs-link-text-color hover:underline"
href={field?.type?.name || '#'}
onClick={(e) => clinkHandler(e, field?.type?.name as string)}
>
{field?.type?.name}
</a>
</li>
);
});
const isFields = fields || inputFields;
const enumValues = currType?.enumValues?.map((value) => {
return <li key={value.name}>{value.name}</li>;
});
const possibleTypes = currType?.possibleTypes?.map((type) => {
return (
<li key={type.name}>
<a
className="text-docs-link-text-color hover:underline"
href={type.name}
onClick={(e) => clinkHandler(e, type.name)}
>
{type.name}
</a>
</li>
);
});
const interfaces = currType?.interfaces?.map((inter) => {
return (
<li key={inter.name}>
<a
className="text-docs-link-text-color hover:underline"
href={inter.name}
onClick={(e) => clinkHandler(e, inter.name)}
>
{inter.name}
</a>
</li>
);
});
const rootRef = useScrollbar<HTMLDivElement>();
return (
<div ref={rootRef} className="h-full">
<div className="p-10 py-[56px] text-left text-on-surface sm:px-[56px]">
<BackDocsBtn onClick={() => explorer.back()} title={explorer.prev()} />
<h2 className="mt-8 w-full text-3xl">{currType?.name}</h2>
<p className="mt-8">{currType?.description}</p>
{interfaces && interfaces?.length > 0 && <h3 className="text-xl">Implements:</h3>}
<ul className="mt-8 flex flex-col gap-5">{interfaces}</ul>
{isFields && <h3 className="text-xl">Fields:</h3>}
<ul className="mt-8 flex flex-col gap-5">{fields}</ul>
<ul className="mt-8 flex flex-col gap-5">{inputFields}</ul>
{enumValues && <h3 className="text-xl">Enum values:</h3>}
<ul className="mt-8 flex flex-col gap-5">{enumValues}</ul>
{possibleTypes && <h3 className="text-xl">Implementations</h3>}
<ul className="mt-8 flex flex-col gap-5">{possibleTypes}</ul>
</div>
</div>
);
};

export default DocsTypeComp;
Loading

0 comments on commit 4a94030

Please sign in to comment.