WIP: New User Guide (#3984)

* initial commit

* Theme setup on twenty-website package

* Left bar, Content done

* Content added, useDeviceType hook added

* useDeviceType file renamed

* Responsiveness introduced

* Mobile responsiveness fix

* TOC layout

* PR fixes

* PR changes 2

* PR changes #3
This commit is contained in:
Kanav Arora
2024-02-23 21:09:48 +05:30
committed by GitHub
parent 35a2178cde
commit 4b22c0404e
33 changed files with 914 additions and 161 deletions

View File

@ -1,121 +0,0 @@
import { FunctionComponent } from 'react';
import * as TablerIcons from '@tabler/icons-react';
import Link from 'next/link';
import { ContentContainer } from '@/app/components/ContentContainer';
import { Directory, FileContent, getPosts } from '@/app/get-posts';
function loadIcon(iconName?: string) {
const name = iconName ? iconName : 'IconCategory';
try {
const icon = TablerIcons[
name as keyof typeof TablerIcons
] as FunctionComponent;
return icon as TablerIcons.Icon;
} catch (error) {
console.error('Icon not found:', iconName);
return null;
}
}
const DirectoryItem = ({
name,
item,
}: {
name: string;
item: Directory | FileContent;
}) => {
if ('content' in item) {
// If the item is a file, we render a link.
const Icon = loadIcon(item.itemInfo.icon);
return (
<div key={name}>
<Link
style={{
textDecoration: 'none',
color: '#333',
display: 'flex',
alignItems: 'center',
gap: '8px',
}}
href={
item.itemInfo.path != 'user-guide/home'
? `/user-guide/${item.itemInfo.path}`
: '/user-guide'
}
>
{Icon ? <Icon size={12} /> : ''}
{item.itemInfo.title}
</Link>
</div>
);
} else {
// If the item is a directory, we render the title and the items in the directory.
return (
<div key={name}>
<h4 style={{ textTransform: 'uppercase', color: '#B3B3B3' }}>
{item.itemInfo.title}
</h4>
{Object.entries(item).map(([childName, childItem]) => {
if (childName !== 'itemInfo') {
return (
<DirectoryItem
key={childName}
name={childName}
item={childItem as Directory | FileContent}
/>
);
}
})}
</div>
);
}
};
export default async function UserGuideHome({
children,
}: {
children: React.ReactNode;
}) {
const basePath = '/src/content/user-guide';
const posts = await getPosts(basePath);
return (
<ContentContainer>
<div style={{ display: 'flex', flexDirection: 'row' }}>
<div
style={{
borderRight: '1px solid rgba(20, 20, 20, 0.08)',
paddingRight: '24px',
minWidth: '200px',
paddingTop: '48px',
}}
>
{posts['home.mdx'] && (
<DirectoryItem
name="home"
item={posts['home.mdx'] as FileContent}
/>
)}
{Object.entries(posts).map(([name, item]) => {
if (name !== 'itemInfo' && name != 'home.mdx') {
return (
<DirectoryItem
key={name}
name={name}
item={item as Directory | FileContent}
/>
);
}
})}
</div>
<div style={{ paddingLeft: '24px', paddingRight: '200px' }}>
{children}
</div>
</div>
</ContentContainer>
);
}

View File

@ -1,21 +0,0 @@
import { getPost } from '@/app/get-posts';
export default async function UserGuideHome({
params,
}: {
params: { slug: string[] };
}) {
const basePath = '/src/content/user-guide';
const mainPost = await getPost(
params.slug && params.slug.length ? params.slug : ['home'],
basePath,
);
return (
<div>
<h2>{mainPost?.itemInfo.title}</h2>
<div>{mainPost?.content}</div>
</div>
);
}

View File

@ -0,0 +1,16 @@
import UserGuideContent from '@/app/components/user-guide/UserGuideContent';
import { getPost } from '@/app/get-posts';
export default async function UserGuideSlug({
params,
}: {
params: { slug: string };
}) {
const basePath = '/src/content/user-guide';
const mainPost = await getPost(
params.slug && params.slug.length ? params.slug : 'home',
basePath,
);
return mainPost && <UserGuideContent item={mainPost} />;
}

View File

@ -0,0 +1,34 @@
export type UserGuideHomeCardsType = {
url: string;
title: string;
subtitle: string;
image: string;
};
export const UserGuideHomeCards: UserGuideHomeCardsType[] = [
{
url: 'what-is-twenty',
title: 'What is Twenty',
subtitle:
"A brief on Twenty's commitment to reshaping CRM with Open Source",
image: '/images/user-guide/home/what-is-twenty.png',
},
{
url: 'create-a-workspace',
title: 'Create a Workspace',
subtitle: 'Custom objects store unique info in workspaces.',
image: '/images/user-guide/home/create-a-workspace.png',
},
{
url: 'import-your-data',
title: 'Import your data',
subtitle: 'Easily create a note to keep track of important information.',
image: '/images/user-guide/home/import-your-data.png',
},
{
url: 'custom-objects',
title: 'Custom Objects',
subtitle: 'Custom objects store unique info in workspaces.',
image: '/images/user-guide/home/custom-objects.png',
},
];

View File

@ -0,0 +1,29 @@
export type IndexSubtopic = {
title: string;
url: string;
};
export type IndexHeading = {
[heading: string]: IndexSubtopic[];
};
export const UserGuideIndex = {
'Getting Started': [
{ title: 'What is Twenty', url: 'what-is-twenty' },
{ title: 'Create a Workspace', url: 'create-a-workspace' },
{ title: 'Import your data', url: 'import-your-data' },
],
Objects: [
{ title: 'People', url: 'people' },
{ title: 'Companies', url: 'companies' },
{ title: 'Opportunities', url: 'opportunities' },
{ title: 'Custom Objects', url: 'custom-objects' },
{ title: 'Remote Objects', url: 'remote-objects' },
],
Functions: [
{ title: 'Email', url: 'email' },
{ title: 'Calendar', url: 'calendar' },
{ title: 'Notes', url: 'notes' },
{ title: 'Tasks', url: 'tasks' },
],
};

View File

@ -0,0 +1,40 @@
'use client';
import { ReactNode } from 'react';
import styled from '@emotion/styled';
import { usePathname } from 'next/navigation';
import UserGuideSidebar from '@/app/components/user-guide/UserGuideSidebar';
import UserGuideTableContents from '@/app/components/user-guide/UserGuideTableContents';
import { Theme } from '@/app/ui/theme/theme';
import { DeviceType, useDeviceType } from '@/app/ui/utilities/useDeviceType';
const StyledContainer = styled.div`
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between:
border-bottom: 1px solid ${Theme.background.transparent.medium};
`;
const StyledEmptySideBar = styled.div`
width: 20%;
`;
export default function UserGuideLayout({ children }: { children: ReactNode }) {
const pathname = usePathname();
const deviceType = useDeviceType();
return (
<StyledContainer>
{deviceType !== DeviceType.MOBILE && <UserGuideSidebar />}
{children}
{deviceType !== DeviceType.DESKTOP ? (
<></>
) : pathname === '/user-guide' ? (
<StyledEmptySideBar />
) : (
<UserGuideTableContents />
)}
</StyledContainer>
);
}

View File

@ -0,0 +1,5 @@
import UserGuideMain from '@/app/components/user-guide/UserGuideMain';
export default async function UserGuideHome() {
return <UserGuideMain />;
}