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:
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
16
packages/twenty-website/src/app/user-guide/[slug]/page.tsx
Normal file
16
packages/twenty-website/src/app/user-guide/[slug]/page.tsx
Normal 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} />;
|
||||
}
|
||||
@ -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',
|
||||
},
|
||||
];
|
||||
@ -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' },
|
||||
],
|
||||
};
|
||||
40
packages/twenty-website/src/app/user-guide/layout.tsx
Normal file
40
packages/twenty-website/src/app/user-guide/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
5
packages/twenty-website/src/app/user-guide/page.tsx
Normal file
5
packages/twenty-website/src/app/user-guide/page.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import UserGuideMain from '@/app/components/user-guide/UserGuideMain';
|
||||
|
||||
export default async function UserGuideHome() {
|
||||
return <UserGuideMain />;
|
||||
}
|
||||
Reference in New Issue
Block a user