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

Tembo university merge #711

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
46 changes: 46 additions & 0 deletions src/components/PrevButton.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
import { Image } from 'astro:assets';
import ChevronLeft from '../images/arrow-left.svg';
import { styles } from '../util';

interface Props {
slug: string;
title: string;
label?: string;
subTitle?: string;
classNames?: string;
subTitleStyles?: string;
}
const { slug, title, classNames, subTitle, label, subTitleStyles } =
Astro.props;
---

<a
href={slug}
class={styles('p-4 rounded-lg gradient-next-button', classNames)}
>
<div class='flex flex-col items-start gap-2'>
<div class='flex items-center gap-[5px]'>
<Image
src={ChevronLeft}
height={14}
width={14}
alt='chevron left arrow'
/>
<p class='text-lightGrey font-semibold text-sm'>{label}</p>
</div>
<h4 class='text-white text-start'>{title}</h4>
{
subTitle && (
<p
class={styles(
'text-lightGrey font-semibold text-xs',
subTitleStyles,
)}
>
{subTitle}
</p>
)
}
</div>
</a>
12 changes: 6 additions & 6 deletions src/components/Toc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ interface Props {

// Note: `pl-${depth * 2}` didn't work on deployment preview but worked on localhost, hence this solution for toc indentation
const paddingLeftVariants: { [key: number]: string } = {
1: "pl-2",
2: "pl-4",
3: "pl-6",
4: "pl-8",
5: "pl-10",
6: "pl-12"
1: 'pl-2',
2: 'pl-4',
3: 'pl-6',
4: 'pl-8',
5: 'pl-10',
6: 'pl-12',
};

const Toc: React.FC<Props> = ({
Expand Down
63 changes: 63 additions & 0 deletions src/components/docs/Breadcrumb.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
import { Image } from 'astro:assets';
import ArrowRightWhiteIcon from '../../images/arrow-right-white.svg';
import { getCollection } from 'astro:content';

const allPages = await getCollection('university');
const validPaths = new Set(allPages.map((page) => '/university/' + page.slug));

const currentPath = Astro.url.pathname;
const pathSegments = currentPath.split('/').filter((segment) => segment !== '');
pathSegments.shift();

interface BreadcrumbItem {
label: string;
href: string;
isValidPage: boolean;
}

const breadcrumbs: BreadcrumbItem[] = pathSegments.map((segment, index) => {
const path = '/' + pathSegments.slice(0, index + 1).join('/');
return {
label:
segment.charAt(0).toUpperCase() +
segment.slice(1).replace(/-/g, ' '),
href: path,
isValidPage: validPaths.has(path),
};
});
---

<div class='flex items-center gap-x-4'>
{
breadcrumbs.map((item, index) => (
<>
{index > 0 && (
<Image
src={ArrowRightWhiteIcon}
alt='arrow right'
width='16'
height='16'
class='move-right-icon'
/>
)}
{index === breadcrumbs.length - 1 ? (
<span class='bg-charcoal text-white font-medium leading-6 text-sm rounded-full py-1 px-3'>
{item.label}
</span>
) : item.isValidPage ? (
<a
href={item.href}
class='text-white font-semibold text-sm leading-6'
>
{item.label}
</a>
) : (
<span class='text-white font-semibold text-sm leading-6'>
{item.label}
</span>
)}
</>
))
}
</div>
168 changes: 168 additions & 0 deletions src/components/docs/university/SidebarItems.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import React, { useState, useEffect } from 'react';
import type { CollectionEntry } from 'astro:content';
import sidebarConfig from './sidebarConfig.json';
interface SidebarItem {
title: string;
entry?: CollectionEntry<'university'>;
[key: string]:
| SidebarItem
| string
| CollectionEntry<'university'>
| undefined;
}

interface SidebarItemsProps {
structure: SidebarItem;
currentPath: string;
isTopLevel: boolean;
}

const SidebarItems: React.FC<SidebarItemsProps> = ({
structure,
currentPath,
isTopLevel,
}) => {
const [openItems, setOpenItems] = useState<Set<string>>(new Set());

useEffect(() => {
// Open items based on the current path
const pathParts = currentPath.split('/').filter(Boolean).slice(1);
let currentItem = structure;
const newOpenItems = new Set<string>();

for (const part of pathParts) {
if (currentItem[part]) {
newOpenItems.add(part);
currentItem = currentItem[part] as SidebarItem;
} else {
break;
}
}

setOpenItems(newOpenItems);
}, [currentPath, structure]);

const renderSidebarItem = (
key: string,
value: SidebarItem,
path: string[] = [],
) => {
const slug = value.entry?.slug;
const isActive = currentPath.includes(slug || '#');
const hasChildren = Object.keys(value).some(
(key) => key !== 'title' && key !== 'indexEntry' && key !== 'entry',
);
const itemPath = [...path, key];
const isOpen = openItems.has(key);

const displayTitle =
value.entry?.data.sideBarTitle ||
value.title ||
sidebarConfig.folderTitles[
key as keyof typeof sidebarConfig.folderTitles
] ||
key;

const toggleOpen = (e: React.MouseEvent) => {
e.stopPropagation();
setOpenItems((prev) => {
const newSet = new Set(prev);
if (newSet.has(key)) {
newSet.delete(key);
} else {
newSet.add(key);
}
return newSet;
});
};

const handleClick = (e: React.MouseEvent) => {
if (hasChildren && !slug) {
toggleOpen(e);
}
};

return (
<li key={itemPath.join('/')} className='flex flex-col w-full'>
<div className='flex items-center w-full'>
{slug ? (
<a
href={`/university/${slug}`}
className={`flex justify-between items-center font-secondary text-lightGrey hover:text-white transition-all duration-100 text-sm flex-grow py-1 ${isActive ? 'text-white' : ''}`}
>
{displayTitle}
{slug.split('/').length > 1 &&
slug.split('/')[0] === 'courses' &&
isTopLevel && (
<img
src={
isOpen
? '/arrow-down.svg'
: '/arrow-right.svg'
}
height={14}
width={14}
alt='chevron arrow'
className='transition-all duration-150 ease-in-out opacity-100'
/>
)}
</a>
) : (
<span
onClick={handleClick}
className={`font-secondary text-lightGrey hover:text-white transition-all duration-100 text-sm flex-grow py-1 cursor-pointer ${isActive ? 'text-white' : ''}`}
>
{displayTitle}
</span>
)}
{hasChildren && (
<button
onClick={toggleOpen}
className='ml-2 p-1 focus:outline-none'
>
<img
src={
isOpen
? '/arrow-down.svg'
: '/arrow-right.svg'
}
height={14}
width={14}
alt='chevron arrow'
className='transition-all duration-150 ease-in-out opacity-100'
/>
</button>
)}
</div>
{hasChildren && isOpen && (
<ul className='flex flex-col border-l-[#EAEAEA33] border-l-[1px] space-y-2 pl-4 mt-1'>
{Object.entries(value)
.filter(
([subKey]) =>
subKey !== 'title' &&
subKey !== 'indexEntry' &&
subKey !== 'entry',
)
.map(([subKey, subValue]) =>
renderSidebarItem(
subKey,
subValue as SidebarItem,
itemPath,
),
)}
</ul>
)}
</li>
);
};

return (
<ul className='flex flex-col gap-2 w-full'>
{Object.entries(structure).map(([key, value]) =>
renderSidebarItem(key, value as SidebarItem),
)}
</ul>
);
};

export default SidebarItems;
72 changes: 72 additions & 0 deletions src/components/docs/university/UniversitySideBar.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
import type { CollectionEntry } from 'astro:content';
import { getCollection } from 'astro:content';
import Logo from '../../../images/logoWithText.svg';
import { Image } from 'astro:assets';
import Home from '../../../images/home.svg';
import SidebarItems from './SidebarItems';
import Line from '../Line.astro';
import {
buildSidebarStructure,
sortSidebarStructure,
getDisplayStructure,
} from '../../../util/universityDocsUtils';

const allUniversityContent = await getCollection('university');

const sidebarStructure = await buildSidebarStructure(allUniversityContent);
const sortedSidebarStructure = sortSidebarStructure(sidebarStructure);

const currentPath = Astro.url.pathname;
const pathParts = currentPath.split('/').filter(Boolean);

const displayStructure = getDisplayStructure(sortedSidebarStructure, pathParts);
---

<nav
class='border-r border-r-[#EAEAEA33] max-w-[280px] w-full hidden min-[1000px]:flex transition-all duration-150 ease-in'
>
<div class='h-screen relative w-full overflow-y-scroll'>
<div class='sticky top-0 px-8 z-10 mb-2'>
<a
href={'/university'}
class='flex items-center gap-2 bg-offBlack pt-8 pb-6'
>
<Image
src={Logo}
alt='tembo elephant logo and name'
width={120}
/>
<span
class='bg-gradient-to-r from-salmon via-purple to-lightPurple inline-block text-transparent bg-clip-text text-xs uppercase'
>University</span
>
</a>
<div
class='bg-gradient-to-b from-offBlack to-transparent h-4 w-full'
>
</div>
</div>
<div
class='flex flex-col px-8 pb-8 transition-all duration-200 ease-out z-1'
>
<a
href={`/university`}
class='font-secondary font-extrabold flex items-center gap-4'
>
<Image src={Home} alt='home' width={20} />
University
</a>

<div class='my-6'>
<Line />
</div>
<SidebarItems
structure={displayStructure}
currentPath={currentPath}
isTopLevel={pathParts.length <= 2}
client:load
/>
</div>
</div>
</nav>
7 changes: 7 additions & 0 deletions src/components/docs/university/sidebarConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"folderTitles": {
"building-a-foundation": "Building a Foundation",
"url-shortener": "URL Shortener",
"introduction": "Introduction"
}
}
7 changes: 7 additions & 0 deletions src/content/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ const university = defineCollection({
uppercase: z.boolean().default(false),
uppercaseParent: z.boolean().default(false),
tags: z.array(z.string()).max(4).optional(),
nextDoc: z
.object({
title: z.string(),
slug: z.string(),
subTitle: z.string().optional(),
})
.optional(),
}),
});

Expand Down
Loading