Merge branch 'main' into blog-section

This commit is contained in:
Kalidou Diagne
2025-04-09 09:06:51 +03:00
85 changed files with 1961 additions and 1522 deletions

View File

@@ -1,6 +1,6 @@
import { ReactNode } from 'react'
import { ReactNode } from "react"
import { AppContent } from './ui/app-content'
import { AppContent } from "./ui/app-content"
type BannerProps = {
title: ReactNode
@@ -14,7 +14,7 @@ const Banner = ({ title, subtitle, children }: BannerProps) => {
<div className="py-16">
<AppContent className="flex flex-col gap-6">
<div className="flex flex-col items-center text-center">
{typeof title === 'string' ? (
{typeof title === "string" ? (
<h6 className="py-4 font-sans text-base font-bold uppercase tracking-[4px] text-tuatara-950">
{title}
</h6>
@@ -30,6 +30,6 @@ const Banner = ({ title, subtitle, children }: BannerProps) => {
)
}
Banner.displayName = 'Banner'
Banner.displayName = "Banner"
export { Banner }

View File

@@ -1,4 +1,4 @@
import NextLink from 'next/link'
import NextLink from "next/link"
interface Props {
path: string[]
@@ -17,8 +17,8 @@ const Breadcrumbs = ({ path }: Props) => {
)
}
return (
<NextLink key={index} href={`/${item}`} className={'capitalize'}>
{item === 'projects' ? 'All Projects' : item}
<NextLink key={index} href={`/${item}`} className={"capitalize"}>
{item === "projects" ? "All Projects" : item}
<span className="mx-2">/</span>
</NextLink>
)

View File

@@ -1,17 +1,17 @@
import { HTMLAttributes } from 'react'
import { HTMLAttributes } from "react"
import { cn } from '@/lib/utils'
import { cn } from "@/lib/utils"
const Section = ({ children, className }: HTMLAttributes<HTMLDivElement>) => {
return (
<div className={cn('divide-y divide-tuatara-300 w-full', className)}>
<div className={cn("divide-y divide-tuatara-300 w-full", className)}>
{children}
</div>
)
}
const Divider = {
displayName: 'Divider',
displayName: "Divider",
Section,
}

View File

@@ -5,7 +5,7 @@ import {
SunMedium,
X,
type Icon as LucideIcon,
} from 'lucide-react'
} from "lucide-react"
export type Icon = LucideIcon
@@ -114,9 +114,9 @@ export const Icons = {
<svg
width={props.width}
height={
typeof props.height === 'number'
typeof props.height === "number"
? props.height
: typeof props.width === 'number'
: typeof props.width === "number"
? props.width - 1
: 18
}
@@ -151,9 +151,9 @@ export const Icons = {
xmlns="http://www.w3.org/2000/svg"
width={props.width}
height={
typeof props.height === 'number'
typeof props.height === "number"
? props.height
: typeof props.width === 'number'
: typeof props.width === "number"
? props.width - 2
: 22
}
@@ -172,9 +172,9 @@ export const Icons = {
xmlns="http://www.w3.org/2000/svg"
width={props.width}
height={
typeof props.height === 'number'
typeof props.height === "number"
? props.height
: typeof props.width === 'number'
: typeof props.width === "number"
? props.width + 2
: 26
}
@@ -209,9 +209,9 @@ export const Icons = {
<svg
width={props.size}
height={
typeof props.size === 'number'
typeof props.size === "number"
? props.size
: typeof props.size === 'number'
: typeof props.size === "number"
? props.size + 1
: 17
}
@@ -235,9 +235,9 @@ export const Icons = {
<svg
width={props.width}
height={
typeof props.height === 'number'
typeof props.height === "number"
? props.height
: typeof props.width === 'number'
: typeof props.width === "number"
? props.width - 1
: 19
}
@@ -256,9 +256,9 @@ export const Icons = {
<svg
width={props.width}
height={
typeof props.height === 'number'
typeof props.height === "number"
? props.height
: typeof props.width === 'number'
: typeof props.width === "number"
? props.width - 1
: 19
}
@@ -277,9 +277,9 @@ export const Icons = {
<svg
width={props.width}
height={
typeof props.height === 'number'
typeof props.height === "number"
? props.height
: typeof props.width === 'number'
: typeof props.width === "number"
? props.width + 1
: 15
}

View File

@@ -1,4 +1,4 @@
import { Icons } from '../icons'
import { Icons } from "../icons"
export const ProjectLinkIconMap: Record<string, any> = {
github: (

View File

@@ -13,7 +13,7 @@ import {
import { cn } from "@/lib/utils"
import { LocaleTypes } from "@/app/i18n/settings"
import { ProjectLink } from "./project-link"
import { ProjectLink } from "../mappings/project-link"
interface ProjectCardProps
extends React.HTMLAttributes<HTMLDivElement>,

View File

@@ -1,17 +1,17 @@
'use client'
"use client"
import { HtmlHTMLAttributes } from 'react'
import Link from 'next/link'
import { HtmlHTMLAttributes } from "react"
import Link from "next/link"
import {
FilterLabelMapping,
ProjectFilter,
} from '@/state/useProjectFiltersState'
} from "@/state/useProjectFiltersState"
import { ProjectInterface } from '@/lib/types'
import { useTranslation } from '@/app/i18n/client'
import { LocaleTypes } from '@/app/i18n/settings'
import { ProjectInterface } from "@/lib/types"
import { useTranslation } from "@/app/i18n/client"
import { LocaleTypes } from "@/app/i18n/settings"
import { CategoryTag } from '../ui/categoryTag'
import { CategoryTag } from "../ui/categoryTag"
interface TagsProps extends HtmlHTMLAttributes<HTMLDivElement> {
label: string
@@ -32,7 +32,7 @@ type IProjectTags = {
}
export function ProjectTags({ project, lang }: IProjectTags) {
const { t } = useTranslation(lang, 'common')
const { t } = useTranslation(lang, "common")
return (
<div className="flex flex-col gap-4 pt-10">
@@ -40,7 +40,7 @@ export function ProjectTags({ project, lang }: IProjectTags) {
const keyTags = project?.tags?.[key as ProjectFilter]
const hasItems = keyTags && keyTags?.length > 0
if (['themes', 'builtWith'].includes(key)) return null // keys to ignore
if (["themes", "builtWith"].includes(key)) return null // keys to ignore
return (
hasItems && (
<div data-section-id={key} key={key}>

View File

@@ -1,17 +1,17 @@
'use client'
"use client"
import React from 'react'
import Link from 'next/link'
import React from "react"
import Link from "next/link"
import {
ActionLinkTypeLink,
ProjectExtraLinkType,
ProjectInterface,
} from '@/lib/types'
import { useTranslation } from '@/app/i18n/client'
import { LocaleTypes } from '@/app/i18n/settings'
} from "@/lib/types"
import { useTranslation } from "@/app/i18n/client"
import { LocaleTypes } from "@/app/i18n/settings"
import { Icons } from '../icons'
import { Icons } from "../icons"
interface ProjectExtraLinksProps {
project: ProjectInterface
@@ -27,7 +27,7 @@ export default function ProjectExtraLinks({
project,
lang,
}: ProjectExtraLinksProps) {
const { t } = useTranslation(lang, 'common')
const { t } = useTranslation(lang, "common")
const { extraLinks = {} } = project
const hasExtraLinks = Object.keys(extraLinks).length > 0
@@ -39,19 +39,19 @@ export default function ProjectExtraLinks({
}
> = {
buildWith: {
label: t('buildWithThisTool'),
label: t("buildWithThisTool"),
icon: <Icons.hammer />,
},
play: {
label: t('tryItOut'),
label: t("tryItOut"),
icon: <Icons.hand />,
},
research: {
label: t('deepDiveResearch'),
label: t("deepDiveResearch"),
icon: <Icons.readme />,
},
learn: {
label: t('learnMore'),
label: t("learnMore"),
},
}

View File

@@ -1,20 +1,20 @@
'use client'
"use client"
import React, { ChangeEvent, ReactNode, useEffect, useState } from 'react'
import Image from 'next/image'
import { useRouter, useSearchParams } from 'next/navigation'
import { projects } from '@/data/projects'
import FiltersIcon from '@/public/icons/filters.svg'
import React, { ChangeEvent, ReactNode, useEffect, useState } from "react"
import Image from "next/image"
import { useRouter, useSearchParams } from "next/navigation"
import { projects } from "@/data/projects"
import FiltersIcon from "@/public/icons/filters.svg"
import {
FilterLabelMapping,
FilterTypeMapping,
ProjectFilter,
useProjectFiltersState,
} from '@/state/useProjectFiltersState'
import i18next from 'i18next'
import { useDebounce } from 'react-use'
} from "@/state/useProjectFiltersState"
import i18next from "i18next"
import { useDebounce } from "react-use"
import { IThemeStatus, IThemesButton, LangProps } from '@/types/common'
import { IThemeStatus, IThemesButton, LangProps } from "@/types/common"
import {
ProjectCategories,
ProjectCategory,
@@ -22,18 +22,18 @@ import {
ProjectSections,
ProjectStatus,
ProjectStatusLabelMapping,
} from '@/lib/types'
import { cn, queryStringToObject } from '@/lib/utils'
import { useTranslation } from '@/app/i18n/client'
import { LocaleTypes } from '@/app/i18n/settings'
} from "@/lib/types"
import { cn, queryStringToObject } from "@/lib/utils"
import { useTranslation } from "@/app/i18n/client"
import { LocaleTypes } from "@/app/i18n/settings"
import { Icons } from '../icons'
import Badge from '../ui/badge'
import { Button } from '../ui/button'
import { CategoryTag } from '../ui/categoryTag'
import { Checkbox } from '../ui/checkbox'
import { Input } from '../ui/input'
import { Modal } from '../ui/modal'
import { Icons } from "../icons"
import Badge from "../ui/badge"
import { Button } from "../ui/button"
import { CategoryTag } from "../ui/categoryTag"
import { Checkbox } from "../ui/checkbox"
import { Input } from "../ui/input"
import { Modal } from "../ui/modal"
interface FilterWrapperProps {
label: string
@@ -43,7 +43,7 @@ interface FilterWrapperProps {
const FilterWrapper = ({ label, children, className }: FilterWrapperProps) => {
return (
<div className={cn('flex flex-col gap-4 py-6', className)}>
<div className={cn("flex flex-col gap-4 py-6", className)}>
<span className="text-xl font-bold">{label}</span>
{children}
</div>
@@ -51,45 +51,45 @@ const FilterWrapper = ({ label, children, className }: FilterWrapperProps) => {
}
export const ThemesButtonMapping = (lang: LocaleTypes): IThemesButton => {
const t = i18next.getFixedT(lang, 'all')
const t = i18next.getFixedT(lang, "all")
return {
build: {
label: t('tags.build'),
label: t("tags.build"),
icon: <Icons.hammer />,
},
play: {
label: t('tags.play'),
label: t("tags.play"),
icon: <Icons.hand />,
},
research: {
label: t('tags.research'),
label: t("tags.research"),
icon: <Icons.readme />,
},
}
}
export const ThemesStatusMapping = (lang: LocaleTypes): IThemeStatus => {
const t = i18next.getFixedT(lang, 'common')
const t = i18next.getFixedT(lang, "common")
return {
active: {
label: t('status.active'),
label: t("status.active"),
icon: <Icons.checkActive />,
},
inactive: {
label: t('status.inactive'),
label: t("status.inactive"),
icon: <Icons.archived />,
},
}
}
export default function ProjectFiltersBar({ lang }: LangProps['params']) {
const { t } = useTranslation(lang as LocaleTypes, 'common')
export default function ProjectFiltersBar({ lang }: LangProps["params"]) {
const { t } = useTranslation(lang as LocaleTypes, "common")
const [showModal, setShowModal] = useState(false)
const router = useRouter()
const searchParams = useSearchParams()
const [searchQuery, setSearchQuery] = useState('')
const [searchQuery, setSearchQuery] = useState("")
const [filterCount, setFilterCount] = useState(0)
const {
@@ -124,11 +124,11 @@ export default function ProjectFiltersBar({ lang }: LangProps['params']) {
const clearAllFilters = () => {
useProjectFiltersState.setState({
activeFilters: {},
queryString: '',
queryString: "",
projects,
})
setSearchQuery('') // clear input
router.push('/projects')
setSearchQuery("") // clear input
router.push("/projects")
}
useDebounce(
@@ -152,7 +152,7 @@ export default function ProjectFiltersBar({ lang }: LangProps['params']) {
size="sm"
onClick={clearAllFilters}
>
{t('clearAll')}
{t("clearAll")}
</Button>
<div className="ml-auto">
<Button
@@ -160,7 +160,7 @@ export default function ProjectFiltersBar({ lang }: LangProps['params']) {
size="sm"
onClick={() => setShowModal(false)}
>
{t('showProjects')}
{t("showProjects")}
</Button>
</div>
</div>
@@ -171,30 +171,30 @@ export default function ProjectFiltersBar({ lang }: LangProps['params']) {
<div className="flex flex-col divide-y divide-tuatara-200">
{Object.entries(filters).map(([key, items]) => {
const filterLabel =
FilterLabelMapping(lang)?.[key as ProjectFilter] ?? ''
FilterLabelMapping(lang)?.[key as ProjectFilter] ?? ""
const type = FilterTypeMapping?.[key as ProjectFilter]
const hasItems = items.length > 0
const hasActiveThemeFilters =
(activeFilters?.themes ?? [])?.length > 0
if (key === 'themes' && !hasActiveThemeFilters) return null
if (key === "themes" && !hasActiveThemeFilters) return null
return (
hasItems && (
<FilterWrapper key={key} label={filterLabel}>
<div
className={cn('gap-y-2', {
'grid grid-cols-1 gap-2 md:grid-cols-3':
type === 'checkbox',
'flex gap-x-4 flex-wrap': type === 'button',
className={cn("gap-y-2", {
"grid grid-cols-1 gap-2 md:grid-cols-3":
type === "checkbox",
"flex gap-x-4 flex-wrap": type === "button",
})}
>
{items.map((item, index) => {
const isActive =
activeFilters?.[key as ProjectFilter]?.includes(item)
if (type === 'checkbox') {
if (type === "checkbox") {
return (
<Checkbox
key={item}
@@ -212,7 +212,7 @@ export default function ProjectFiltersBar({ lang }: LangProps['params']) {
)
}
if (type === 'button') {
if (type === "button") {
const { icon, label } = ThemesButtonMapping(lang)[item]
if (!isActive) return null
return (
@@ -222,7 +222,7 @@ export default function ProjectFiltersBar({ lang }: LangProps['params']) {
closable
onClose={() => {
toggleFilter({
tag: 'themes',
tag: "themes",
value: item,
searchQuery,
})
@@ -248,7 +248,7 @@ export default function ProjectFiltersBar({ lang }: LangProps['params']) {
})}
<FilterWrapper
className="hidden"
label={t('filterLabels.fundingSource')}
label={t("filterLabels.fundingSource")}
>
{ProjectSections.map((section) => {
const label = ProjectSectionLabelMapping[section]
@@ -257,7 +257,7 @@ export default function ProjectFiltersBar({ lang }: LangProps['params']) {
</FilterWrapper>
<FilterWrapper
className="hidden"
label={t('filterLabels.projectStatus')}
label={t("filterLabels.projectStatus")}
>
{Object.keys(ProjectStatus).map((section: any) => {
// @ts-expect-error - ProjectStatusLabelMapping is not typed
@@ -272,24 +272,25 @@ export default function ProjectFiltersBar({ lang }: LangProps['params']) {
<ul className="flex space-x-6">
<div
className={cn(
'relative block px-2 py-1 text-sm font-medium uppercase transition-colors cursor-pointer hover:text-primary',
"relative block px-2 py-1 text-sm font-medium uppercase transition-colors cursor-pointer hover:text-primary",
currentCategory == null
? 'text-sky-400 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:bg-sky-400'
: ''
? "text-sky-400 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:bg-sky-400"
: ""
)}
onClick={() => setCurrentCategory(null)}
>
All
</div>
{ProjectCategories.map((key) => {
if (key === ProjectCategory.RESEARCH) return null // Research category has now it's own page
return (
<div
key={key}
className={cn(
'relative block px-2 py-1 text-sm font-medium uppercase transition-colors cursor-pointer hover:text-primary',
"relative block px-2 py-1 text-sm font-medium uppercase transition-colors cursor-pointer hover:text-primary",
currentCategory === key
? 'text-sky-400 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:bg-sky-400'
: ''
? "text-sky-400 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:bg-sky-400"
: ""
)}
onClick={() => setCurrentCategory(key as ProjectCategory)}
>
@@ -310,7 +311,7 @@ export default function ProjectFiltersBar({ lang }: LangProps['params']) {
})
}}
value={searchQuery}
placeholder={t('searchProjectPlaceholder')}
placeholder={t("searchProjectPlaceholder")}
/>
<div className="flex items-center gap-3">
<Badge value={filterCount}>
@@ -318,12 +319,12 @@ export default function ProjectFiltersBar({ lang }: LangProps['params']) {
onClick={() => setShowModal(true)}
variant="white"
className={cn({
'border-2 border-anakiwa-950': filterCount > 0,
"border-2 border-anakiwa-950": filterCount > 0,
})}
>
<div className="flex items-center gap-2">
<Image src={FiltersIcon} alt="filter icon" />
<span className="hidden md:block">{t('filters')}</span>
<span className="hidden md:block">{t("filters")}</span>
</div>
</Button>
</Badge>
@@ -333,7 +334,7 @@ export default function ProjectFiltersBar({ lang }: LangProps['params']) {
className="hidden bg-transparent cursor-pointer opacity-85 text-primary hover:opacity-100 disabled:pointer-events-none disabled:opacity-50 md:block"
>
<div className="flex items-center gap-2 border-b-2 border-black">
<span className="text-sm font-medium">{t('clearAll')}</span>
<span className="text-sm font-medium">{t("clearAll")}</span>
</div>
</button>
</div>

View File

@@ -1,12 +1,12 @@
'use client'
"use client"
import React, { useCallback, useEffect, useRef, useState } from 'react'
import Image from 'next/image'
import NoResultIcon from '@/public/icons/no-result.svg'
import { useProjectFiltersState } from '@/state/useProjectFiltersState'
import { cva } from 'class-variance-authority'
import React, { useCallback, useEffect, useRef, useState } from "react"
import Image from "next/image"
import NoResultIcon from "@/public/icons/no-result.svg"
import { useProjectFiltersState } from "@/state/useProjectFiltersState"
import { cva } from "class-variance-authority"
import { LangProps } from '@/types/common'
import { LangProps } from "@/types/common"
import {
ProjectInterface,
ProjectSection,
@@ -14,18 +14,18 @@ import {
ProjectSections,
ProjectStatus,
ProjectStatusDescriptionMapping,
} from '@/lib/types'
import { cn } from '@/lib/utils'
import { useTranslation } from '@/app/i18n/client'
} from "@/lib/types"
import { cn } from "@/lib/utils"
import { useTranslation } from "@/app/i18n/client"
import ProjectCard from './project-card'
import ProjectCard from "./project-card"
const sectionTitleClass = cva(
"relative font-sans text-base font-bold uppercase tracking-[3.36px] text-anakiwa-950 after:ml-8 after:absolute after:top-1/2 after:h-[1px] after:w-full after:translate-y-1/2 after:bg-anakiwa-300 after:content-['']"
)
const NoResults = ({ lang }: LangProps['params']) => {
const { t } = useTranslation(lang, 'common')
const NoResults = ({ lang }: LangProps["params"]) => {
const { t } = useTranslation(lang, "common")
return (
<div className="flex flex-col gap-2 pt-24 pb-40 text-center">
@@ -33,21 +33,21 @@ const NoResults = ({ lang }: LangProps['params']) => {
<Image className="h-9 w-9" src={NoResultIcon} alt="no result icon" />
</div>
<span className="text-2xl font-bold font-display text-tuatara-950">
{t('noResults')}
{t("noResults")}
</span>
<span className="text-lg font-normal text-tuatara-950">
{t('noResultsDescription')}
{t("noResultsDescription")}
</span>
</div>
)
}
const ProjectStatusOrderList = ['active', 'maintained', 'inactive']
const ProjectStatusOrderList = ["active", "maintained", "inactive"]
export const ProjectList = ({ lang }: LangProps['params']) => {
const { t } = useTranslation(lang, 'resources-page')
export const ProjectList = ({ lang }: LangProps["params"]) => {
const { t } = useTranslation(lang, "resources-page")
const SCROLL_OFFSET = -400
const [activeId, setActiveId] = useState('')
const [activeId, setActiveId] = useState("")
const [isManualScroll, setIsManualScroll] = useState(false)
const [isMounted, setIsMounted] = useState(false)
@@ -61,8 +61,8 @@ export const ProjectList = ({ lang }: LangProps['params']) => {
useEffect(() => {
setIsMounted(true)
if (typeof window !== 'undefined') {
sectionsRef.current = document.querySelectorAll('div[data-section]')
if (typeof window !== "undefined") {
sectionsRef.current = document.querySelectorAll("div[data-section]")
const handleScroll = () => {
if (isManualScroll) return
@@ -70,18 +70,18 @@ export const ProjectList = ({ lang }: LangProps['params']) => {
sectionsRef.current?.forEach((section: any) => {
const sectionTop = section.offsetTop - SCROLL_OFFSET
if (window.scrollY >= sectionTop && window.scrollY > 0) {
setActiveId(section.getAttribute('id'))
setActiveId(section.getAttribute("id"))
}
})
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
window.addEventListener("scroll", handleScroll)
return () => window.removeEventListener("scroll", handleScroll)
}
}, [SCROLL_OFFSET, isManualScroll])
const scrollToId = useCallback((id: string) => {
if (typeof window !== 'undefined') {
if (typeof window !== "undefined") {
const element = document.getElementById(id)
const top = element?.offsetTop ?? 0
@@ -89,7 +89,7 @@ export const ProjectList = ({ lang }: LangProps['params']) => {
setActiveId(id) // active clicked id
setIsManualScroll(true) // tell the window event listener to ignore this scrolling
window?.scrollTo({
behavior: 'smooth',
behavior: "smooth",
top: (top ?? 0) - SCROLL_OFFSET,
})
}
@@ -98,7 +98,7 @@ export const ProjectList = ({ lang }: LangProps['params']) => {
}
}, [])
const hasActiveFilters = searchQuery !== '' || queryString !== ''
const hasActiveFilters = searchQuery !== "" || queryString !== ""
// loading state skeleton
if (!isMounted) {
@@ -169,7 +169,7 @@ export const ProjectList = ({ lang }: LangProps['params']) => {
data-section={status}
className="flex justify-between gap-10"
>
<div className={cn('flex w-full flex-col gap-10 pt-10')}>
<div className={cn("flex w-full flex-col gap-10 pt-10")}>
{!hasActiveFilters && (
<div className="flex flex-col gap-6 overflow-hidden">
<h3 className={cn(sectionTitleClass())}>{status}</h3>
@@ -199,7 +199,7 @@ export const ProjectList = ({ lang }: LangProps['params']) => {
<div id="sidebar" className="sticky hidden p-8 top-20 bg-white/30">
<div className="flex flex-col gap-4">
<h6 className="text-lg font-bold font-display text-tuatara-700">
{t('onThisPage')}
{t("onThisPage")}
</h6>
<ul className="font-sans text-black text-normal">
{ProjectSections.map((id: ProjectSection) => {
@@ -217,9 +217,9 @@ export const ProjectList = ({ lang }: LangProps['params']) => {
}}
data-id={id}
className={cn(
'flex h-8 cursor-pointer items-center border-l-2 border-l-anakiwa-200 px-3 duration-200',
"flex h-8 cursor-pointer items-center border-l-2 border-l-anakiwa-200 px-3 duration-200",
{
'border-l-anakiwa-500 text-anakiwa-500 font-medium':
"border-l-anakiwa-500 text-anakiwa-500 font-medium":
active,
}
)}

View File

@@ -1,22 +1,22 @@
'use client'
"use client"
import {
DEFAULT_PROJECT_SORT_BY,
ProjectFilter,
ProjectSortBy,
useProjectFiltersState,
} from '@/state/useProjectFiltersState'
} from "@/state/useProjectFiltersState"
import { LangProps } from '@/types/common'
import { useTranslation } from '@/app/i18n/client'
import { LangProps } from "@/types/common"
import { useTranslation } from "@/app/i18n/client"
import { CategoryTag } from '../ui/categoryTag'
import { Dropdown } from '../ui/dropdown'
import { CategoryTag } from "../ui/categoryTag"
import { Dropdown } from "../ui/dropdown"
const labelClass = 'h-5 text-xs text-base md:h-6 text-slate-900/70 md:text-sm'
const labelClass = "h-5 text-xs text-base md:h-6 text-slate-900/70 md:text-sm"
export const ProjectResultBar = ({ lang }: LangProps['params']) => {
const { t } = useTranslation(lang, 'common')
export const ProjectResultBar = ({ lang }: LangProps["params"]) => {
const { t } = useTranslation(lang, "common")
const { activeFilters, toggleFilter, projects, sortProjectBy, sortBy } =
useProjectFiltersState((state) => state)
@@ -25,20 +25,20 @@ export const ProjectResultBar = ({ lang }: LangProps['params']) => {
)
const resultLabel = t(
haveActiveFilters ? 'showingProjectsWith' : 'showingProjects',
haveActiveFilters ? "showingProjectsWith" : "showingProjects",
{
count: projects?.length,
}
)
const projectSortItems: { label: string; value: ProjectSortBy }[] = [
{ label: t('filterOptions.random'), value: 'random' },
{ label: t('filterOptions.asc'), value: 'asc' },
{ label: t('filterOptions.desc'), value: 'desc' },
{ label: t("filterOptions.random"), value: "random" },
{ label: t("filterOptions.asc"), value: "asc" },
{ label: t("filterOptions.desc"), value: "desc" },
// { label: t("filterOptions.relevance"), value: "relevance" },
]
const activeSortOption = t('sortBy', {
const activeSortOption = t("sortBy", {
option: t(`filterOptions.${sortBy}`),
})

View File

@@ -0,0 +1,183 @@
import React from "react"
import Image from "next/image"
import { useRouter } from "next/navigation"
import { VariantProps, cva } from "class-variance-authority"
import { getProjectById } from "@/lib/projectsUtils"
import {
ProjectInterface,
ProjectLinkWebsite,
ProjectStatus,
ProjectStatusLabelMapping,
} from "@/lib/types"
import { cn } from "@/lib/utils"
import { LocaleTypes } from "@/app/i18n/settings"
import { ProjectLink } from "../mappings/project-link"
interface ProjectCardProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof projectCardVariants> {
project: ProjectInterface
showLinks?: boolean // show links in the card
showBanner?: boolean // show images in the card
showCardTags?: boolean // show card tags in the card
showStatus?: boolean // show status in the card
contentClassName?: string // show status in the card
}
const tagCardVariants = cva(
"text-xs font-sans text-tuatara-950 rounded-[3px] py-[2px] px-[6px]",
{
variants: {
variant: {
primary: "bg-[#D8FEA8]",
secondary: "bg-[#C2E8F5]",
},
},
}
)
const projectCardVariants = cva(
"flex flex-col overflow-hidden rounded-lg transition duration-200 ease-in border border-transparent",
{
variants: {
showLinks: {
true: "min-h-[280px]",
false: "min-h-[200px]",
},
border: {
true: "border border-slate-900/20",
},
},
}
)
export const ProjectStatusColorMapping: Record<ProjectStatus, string> = {
active: "#D8FEA8",
inactive: "#FFB7AA",
maintained: "#FFEC9E",
}
export default function ProjectCard({
project,
showLinks = false,
showBanner = false,
border = false,
showCardTags = true,
showStatus = true,
className,
contentClassName,
lang,
}: ProjectCardProps & { lang: LocaleTypes }) {
const router = useRouter()
const { id, image, links, name, imageAlt, projectStatus, cardTags } =
project ?? {}
const { content: projectContent } = getProjectById(id, lang)
return (
<div
className={cn(
"group cursor-pointer",
projectCardVariants({ showLinks, border, className })
)}
onClick={() => {
router.push(`/projects/${id}`)
}}
>
{showBanner && (
<div
className="relative flex flex-col border-b border-black/10 cursor-pointer"
onClick={() => {
router.push(`/projects/${id}`)
}}
>
<Image
src={`/project-banners/${image ? image : "fallback.webp"}`}
alt={`${name} banner`}
width={1200}
height={630}
className="h-[160px] w-full overflow-hidden rounded-t-lg border-none object-cover"
/>
{!image && (
<span className="absolute w-full px-5 text-xl font-bold text-center text-black -translate-x-1/2 -translate-y-1/2 left-1/2 top-1/2">
{imageAlt || name}
</span>
)}
</div>
)}
<div
className={cn(
"flex flex-col justify-between h-full gap-8 p-[30px] bg-white rounded-b-lg hover:bg-research-card-gradient duration-300",
contentClassName,
{
"bg-white": !showBanner,
"bg-transparent": showBanner,
}
)}
>
<div className="flex flex-col justify-start gap-2">
<h1 className="text-2xl font-bold leading-7 duration-200 cursor-pointer text-anakiwa-700 line-clamp-2">
{name}
</h1>
{projectContent?.tldr && (
<div className="flex flex-col h-24 gap-4">
<p className="text-slate-900/80 line-clamp-3">
{projectContent?.tldr}
</p>
</div>
)}
</div>
<div className="flex flex-col gap-2">
<div className="flex justify-between ">
{showLinks && (
<div className="flex items-center justify-start gap-3">
{Object.entries(links ?? {})?.map(([website, url], index) => {
return (
<ProjectLink
key={index}
url={url}
website={website as ProjectLinkWebsite}
/>
)
})}
</div>
)}
{showStatus && (
<div
className="px-[6px] py-[2px] text-xs font-normal leading-none flex items-center justify-center rounded-[3px]"
style={{
backgroundColor: ProjectStatusColorMapping[projectStatus],
}}
>
{ProjectStatusLabelMapping[project?.projectStatus]}
</div>
)}
{showCardTags && (
<>
{cardTags && (
<div className="flex items-center gap-1">
{cardTags?.primary && (
<div className={tagCardVariants({ variant: "primary" })}>
{cardTags?.primary}
</div>
)}
{cardTags?.secondary && (
<div
className={tagCardVariants({ variant: "secondary" })}
>
{cardTags?.secondary}
</div>
)}
</div>
)}
</>
)}
</div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,140 @@
"use client"
import React, { useCallback, useEffect, useRef, useState } from "react"
import Image from "next/image"
import NoResultIcon from "@/public/icons/no-result.svg"
import { useProjectFiltersState } from "@/state/useProjectFiltersState"
import { cva } from "class-variance-authority"
import { LangProps } from "@/types/common"
import { ProjectInterface, ProjectStatus } from "@/lib/types"
import { cn } from "@/lib/utils"
import { useTranslation } from "@/app/i18n/client"
import ResearchCard from "./research-card"
import Link from "next/link"
const sectionTitleClass = cva(
"relative font-sans text-base font-bold uppercase tracking-[3.36px] text-anakiwa-950 after:ml-8 after:absolute after:top-1/2 after:h-[1px] after:w-full after:translate-y-1/2 after:bg-anakiwa-300 after:content-['']"
)
const NoResults = ({ lang }: LangProps["params"]) => {
const { t } = useTranslation(lang, "common")
return (
<div className="flex flex-col gap-2 pt-24 pb-40 text-center">
<div className="mx-auto">
<Image className="h-9 w-9" src={NoResultIcon} alt="no result icon" />
</div>
<span className="text-2xl font-bold font-display text-tuatara-950">
{t("noResults")}
</span>
<span className="text-lg font-normal text-tuatara-950">
{t("noResultsDescription")}
</span>
</div>
)
}
const ProjectStatusOrderList = ["active", "maintained", "inactive"]
export const ResearchList = ({ lang }: LangProps["params"]) => {
const { t } = useTranslation(lang, "research-page")
const [isMounted, setIsMounted] = useState(false)
const { researchs, searchQuery, queryString } = useProjectFiltersState(
(state) => state
)
const noItems = researchs?.length === 0
useEffect(() => {
setIsMounted(true)
}, [])
const hasActiveFilters = searchQuery !== "" || queryString !== ""
if (!isMounted) {
return (
<div className="flex flex-col gap-10 lg:px-[100px]">
<div className="flex flex-col gap-6 overflow-hidden">
<div
className={cn(
"after:left-[100px] lg:after:left-[200px]",
sectionTitleClass()
)}
>
<div className="h-3 lg:h-4 w-[120px] lg:w-[220px] bg-gray-200 animate-pulse rounded-lg"></div>
</div>
</div>
<div className="grid items-start justify-between w-full grid-cols-1 gap-2 md:grid-cols-3 md:gap-6 ">
<div className="min-h-[200px] border border-gray-200 bg-gray-200 animate-pulse rounded-lg overflow-hidden"></div>
<div className="min-h-[200px] border border-gray-200 bg-gray-200 animate-pulse rounded-lg overflow-hidden"></div>
<div className="min-h-[200px] border border-gray-200 bg-gray-200 animate-pulse rounded-lg overflow-hidden"></div>
<div className="min-h-[200px] border border-gray-200 bg-gray-200 animate-pulse rounded-lg overflow-hidden"></div>
</div>
</div>
)
}
if (noItems) return <NoResults lang={lang} />
const activeResearchs = researchs.filter(
(research) => research.projectStatus === ProjectStatus.ACTIVE
)
const pastResearchs = researchs.filter(
(research) => research.projectStatus !== ProjectStatus.ACTIVE
)
return (
<div className="relative grid items-start justify-between grid-cols-1">
<div
data-section="active-researchs"
className="flex flex-col justify-between gap-10 lg:px-[100px]"
>
<div className={cn("flex w-full flex-col gap-10")}>
{!hasActiveFilters && (
<div className="flex flex-col gap-6 overflow-hidden">
<h3 className={cn(sectionTitleClass())}>{t("activeResearch")}</h3>
</div>
)}
<div className="grid grid-cols-1 gap-4 md:gap-x-6 md:gap-y-10 lg:grid-cols-3">
{activeResearchs.map((project) => {
return (
<ResearchCard
key={project?.id}
project={project}
lang={lang}
className="h-[180px]"
showBanner={false}
showLinks={false}
showCardTags={false}
showStatus={false}
border
/>
)
})}
</div>
</div>
<div className={cn("flex w-full flex-col gap-10 pt-10")}>
<div className="flex flex-col gap-6 overflow-hidden">
<h3 className={cn(sectionTitleClass())}>{t("pastResearch")}</h3>
</div>
<div className="flex flex-col gap-5">
{pastResearchs.map((project) => {
return (
<Link
href={`/projects/${project?.id}`}
key={project?.id}
className="text-neutral-950 border-b-[2px] border-b-anakiwa-500 text-sm font-medium w-fit hover:text-anakiwa-500 duration-200"
>
{project.name}
</Link>
)
})}
</div>
</div>
</div>
</div>
)
}

View File

@@ -1,16 +1,16 @@
import React from 'react'
import { useEffect, useState } from 'react'
import Link from 'next/link'
import React from "react"
import { useEffect, useState } from "react"
import Link from "next/link"
import { siteConfig } from '@/config/site'
import { AnnounceInterface } from '@/lib/types'
import { convertDirtyStringToHtml } from '@/lib/utils'
import { useTranslation } from '@/app/i18n/client'
import { LocaleTypes } from '@/app/i18n/settings'
import { siteConfig } from "@/config/site"
import { AnnounceInterface } from "@/lib/types"
import { convertDirtyStringToHtml } from "@/lib/utils"
import { useTranslation } from "@/app/i18n/client"
import { LocaleTypes } from "@/app/i18n/settings"
import { Icons } from '../icons'
import { AppContent } from '../ui/app-content'
import { Parser as HtmlToReactParser } from 'html-to-react'
import { Icons } from "../icons"
import { AppContent } from "../ui/app-content"
import { Parser as HtmlToReactParser } from "html-to-react"
interface NewsSectionProps {
lang: LocaleTypes
@@ -28,7 +28,7 @@ const ContentPlaceholder = () => (
)
export const NewsSection = ({ lang }: NewsSectionProps) => {
const { t } = useTranslation(lang, 'news-section')
const { t } = useTranslation(lang, "news-section")
const [newsItems, setNewsItems] = useState<AnnounceInterface[]>([])
const [loading, setLoading] = useState(true)
@@ -36,7 +36,7 @@ export const NewsSection = ({ lang }: NewsSectionProps) => {
useEffect(() => {
const getDiscordAnnouncements = async () => {
setLoading(true)
await fetch('/api/news')
await fetch("/api/news")
.then((res) => res.json())
.then(({ announcements }: { announcements: AnnounceInterface[] }) => {
setNewsItems(announcements ?? [])
@@ -53,7 +53,7 @@ export const NewsSection = ({ lang }: NewsSectionProps) => {
const htmlToReactParser = new HtmlToReactParser()
const announcementContent = htmlToReactParser.parse(
convertDirtyStringToHtml(
news?.content || news?.message_snapshots?.[0]?.message?.content || ''
news?.content || news?.message_snapshots?.[0]?.message?.content || ""
)
)
@@ -65,7 +65,7 @@ export const NewsSection = ({ lang }: NewsSectionProps) => {
<div className="bg-white py-16">
<div className="flex flex-col gap-10 ">
<h3 className="text-base font-bold font-sans text-center uppercase tracking-[3.36px]">
{t('recentUpdates')}
{t("recentUpdates")}
</h3>
<AppContent className="mx-auto flex max-w-[978px] flex-col gap-4">
<div className="flex gap-6 flex-col border border-tuatara-950 bg-anakiwa-100 p-6 rounded-[8px] ">
@@ -74,9 +74,9 @@ export const NewsSection = ({ lang }: NewsSectionProps) => {
news?.timestamp && (
<span className="text-anakiwa-600 text-lg font-bold font-display">
{new Intl.DateTimeFormat(lang, {
month: 'long',
day: 'numeric',
year: 'numeric',
month: "long",
day: "numeric",
year: "numeric",
}).format(new Date(news?.timestamp))}
</span>
)
@@ -93,8 +93,8 @@ export const NewsSection = ({ lang }: NewsSectionProps) => {
>
<Icons.twitter size={24} className="text-anakiwa-500" />
<span className="flex text-anakiwa-900 underline font-medium leading-[24px]">
{t('repostOnSocial', {
socialName: 'X',
{t("repostOnSocial", {
socialName: "X",
})}
</span>
</Link>
@@ -111,7 +111,7 @@ export const NewsSection = ({ lang }: NewsSectionProps) => {
passHref
>
<Icons.discord className="text-anakiwa-400" />
<span>{t('seeAllUpdates')}</span>
<span>{t("seeAllUpdates")}</span>
<Icons.externalUrl className="text-tuatara-950" />
</Link>
</AppContent>
@@ -120,4 +120,4 @@ export const NewsSection = ({ lang }: NewsSectionProps) => {
)
}
NewsSection.displayName = 'NewsSection'
NewsSection.displayName = "NewsSection"

View File

@@ -1,30 +1,30 @@
'use client'
"use client"
import { LangProps } from '@/types/common'
import { useTranslation } from '@/app/i18n/client'
import { LangProps } from "@/types/common"
import { useTranslation } from "@/app/i18n/client"
import { Icons } from '../icons'
import { AppContent } from '../ui/app-content'
import { Icons } from "../icons"
import { AppContent } from "../ui/app-content"
type WhatWeDoContent = { title: string; description: string; icon: any }
export const WhatWeDo = ({ lang }: LangProps['params']) => {
const { t } = useTranslation(lang, 'what-we-do-section')
export const WhatWeDo = ({ lang }: LangProps["params"]) => {
const { t } = useTranslation(lang, "what-we-do-section")
const content: WhatWeDoContent[] = [
{
title: t('privacy.title'),
description: t('privacy.description'),
title: t("privacy.title"),
description: t("privacy.description"),
icon: Icons.privacy,
},
{
title: t('scaling.title'),
description: t('scaling.description'),
title: t("scaling.title"),
description: t("scaling.description"),
icon: Icons.scaling,
},
{
title: t('explorations.title'),
description: t('explorations.description'),
title: t("explorations.title"),
description: t("explorations.description"),
icon: Icons.explorations,
},
]
@@ -35,10 +35,10 @@ export const WhatWeDo = ({ lang }: LangProps['params']) => {
<section className="flex flex-col gap-16 py-16 md:pb-24">
<div className="flex flex-col text-center">
<h6 className="py-6 font-sans text-base font-bold uppercase tracking-[4px] text-tuatara-950">
{t('whatWeDo')}
{t("whatWeDo")}
</h6>
<h3 className="font-display text-[18px] font-bold text-tuatara-950 md:text-3xl">
{t('whatWeDoDescription')}
{t("whatWeDoDescription")}
</h3>
</div>
<div className="grid grid-cols-1 gap-8 lg:grid-cols-3">

View File

@@ -1,17 +1,17 @@
'use client'
"use client"
import Image from 'next/image'
import Link from 'next/link'
import Image from "next/image"
import Link from "next/link"
import { LangProps } from '@/types/common'
import { NavItem } from '@/types/nav'
import { siteConfig } from '@/config/site'
import { cn } from '@/lib/utils'
import { useAppSettings } from '@/hooks/useAppSettings'
import { useTranslation } from '@/app/i18n/client'
import { LangProps } from "@/types/common"
import { NavItem } from "@/types/nav"
import { siteConfig } from "@/config/site"
import { cn } from "@/lib/utils"
import { useAppSettings } from "@/hooks/useAppSettings"
import { useTranslation } from "@/app/i18n/client"
import { Icons } from './icons'
import { AppContent } from './ui/app-content'
import { Icons } from "./icons"
import { AppContent } from "./ui/app-content"
const ItemLabel = ({
label,
@@ -42,11 +42,11 @@ const LinksWrapper = ({
children: React.ReactNode
className?: string
}) => {
return <div className={cn('flex flex-col gap-4', className)}>{children}</div>
return <div className={cn("flex flex-col gap-4", className)}>{children}</div>
}
export function SiteFooter({ lang }: LangProps['params']) {
const { t } = useTranslation(lang, 'common')
export function SiteFooter({ lang }: LangProps["params"]) {
const { t } = useTranslation(lang, "common")
const { MAIN_NAV } = useAppSettings(lang)
@@ -63,7 +63,7 @@ export function SiteFooter({ lang }: LangProps['params']) {
className="h-36 w-36"
/>
<span className="text-center font-sans text-sm leading-[21px] md:text-left">
{t('footer.description')}
{t("footer.description")}
</span>
</div>
<div className="order-2 grid grid-cols-1 justify-between gap-10 uppercase lg:grid-cols-5 lg:gap-0">
@@ -77,7 +77,7 @@ export function SiteFooter({ lang }: LangProps['params']) {
<Link
key={indexKey}
href={external ? href : `/${lang}${href}`}
target={external ? '_blank' : undefined}
target={external ? "_blank" : undefined}
>
<ItemLabel label={title} />
</Link>
@@ -99,7 +99,7 @@ export function SiteFooter({ lang }: LangProps['params']) {
rel="noreferrer"
className="flex items-center gap-2"
>
<ItemLabel label={t('menu.jobs')} external />
<ItemLabel label={t("menu.jobs")} external />
</Link>
<Link
href={siteConfig.links.report}
@@ -107,7 +107,7 @@ export function SiteFooter({ lang }: LangProps['params']) {
rel="noreferrer"
className="flex items-center gap-2"
>
<ItemLabel label={t('menu.report')} external />
<ItemLabel label={t("menu.report")} external />
</Link>
<Link
href={siteConfig.links.firstGoodIssue}
@@ -115,7 +115,7 @@ export function SiteFooter({ lang }: LangProps['params']) {
rel="noreferrer"
className="flex items-center gap-2"
>
<ItemLabel label={t('menu.firstGoodIssue')} external />
<ItemLabel label={t("menu.firstGoodIssue")} external />
</Link>
</LinksWrapper>
<LinksWrapper>
@@ -186,14 +186,14 @@ export function SiteFooter({ lang }: LangProps['params']) {
target="_blank"
rel="noreferrer"
>
<ItemLabel label={t('footer.privacyPolicy')} />
<ItemLabel label={t("footer.privacyPolicy")} />
</Link>
<Link
href={siteConfig.links.termOfUse}
target="_blank"
rel="noreferrer"
>
<ItemLabel label={t('footer.termsOfUse')} />
<ItemLabel label={t("footer.termsOfUse")} />
</Link>
</LinksWrapper>
</div>

View File

@@ -1,4 +1,4 @@
import React from 'react'
import React from "react"
interface MySvgProps {
color: string

View File

@@ -1,15 +1,15 @@
import { cn } from '@/lib/utils'
import { cn } from "@/lib/utils"
interface LabelProps {
label: React.ReactNode
className?: string
}
const SectionTitle = ({ label, className = '' }: LabelProps) => {
const SectionTitle = ({ label, className = "" }: LabelProps) => {
return (
<span
className={cn(
'font-sans text-base font-bold uppercase leading-[24px] tracking-[3.36px] text-tuatara-950',
"font-sans text-base font-bold uppercase leading-[24px] tracking-[3.36px] text-tuatara-950",
className
)}
>
@@ -18,11 +18,11 @@ const SectionTitle = ({ label, className = '' }: LabelProps) => {
)
}
const MainPageTitle = ({ label, className = '' }: LabelProps) => {
const MainPageTitle = ({ label, className = "" }: LabelProps) => {
return (
<span
className={cn(
'text-4xl font-bold break-words font-display text-tuatara-950 lg:text-6xl xl:text-7xl',
"text-4xl font-bold break-words font-display text-tuatara-950 lg:text-6xl xl:text-7xl",
className
)}
>
@@ -32,7 +32,7 @@ const MainPageTitle = ({ label, className = '' }: LabelProps) => {
}
const Label = {
displayName: 'Label',
displayName: "Label",
PageTitle: MainPageTitle,
Section: SectionTitle,
}

View File

@@ -1,12 +1,12 @@
'use client'
"use client"
import { useEffect, useRef, useState } from 'react'
import { useEffect, useRef, useState } from "react"
import { ProjectExtraLinkType } from '@/lib/types'
import { cn } from '@/lib/utils'
import { useTranslation } from '@/app/i18n/client'
import { ProjectExtraLinkType } from "@/lib/types"
import { cn } from "@/lib/utils"
import { useTranslation } from "@/app/i18n/client"
import { Icons } from './icons'
import { Icons } from "./icons"
interface Section {
level: number
@@ -23,11 +23,11 @@ interface WikiSideNavigationProps {
export const WikiSideNavigation = ({
className,
lang = 'en',
content = '',
lang = "en",
content = "",
project,
}: WikiSideNavigationProps) => {
const { t } = useTranslation(lang, 'common')
const { t } = useTranslation(lang, "common")
const [sections, setSections] = useState<Section[]>([])
const [activeSection, setActiveSection] = useState<string | null>(null)
const observerRef = useRef<IntersectionObserver | null>(null)
@@ -45,7 +45,7 @@ export const WikiSideNavigation = ({
extractedSections.push({
level: match[1].length,
text,
id: text.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
id: text.toLowerCase().replace(/[^a-z0-9]+/g, "-"),
})
}
}
@@ -61,14 +61,14 @@ export const WikiSideNavigation = ({
useEffect(() => {
const observerOptions = {
root: null,
rootMargin: '-20% 0px -80% 0px',
rootMargin: "-20% 0px -80% 0px",
threshold: 0,
}
observerRef.current = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setActiveSection(entry.target.getAttribute('data-section-id'))
setActiveSection(entry.target.getAttribute("data-section-id"))
}
})
}, observerOptions)
@@ -96,7 +96,7 @@ export const WikiSideNavigation = ({
window.scrollTo({
top: offsetPosition,
behavior: 'smooth',
behavior: "smooth",
})
setActiveSection(sectionId)
}
@@ -110,19 +110,19 @@ export const WikiSideNavigation = ({
}
> = {
buildWith: {
label: t('buildWith'),
label: t("buildWith"),
icon: <Icons.hammer />,
},
play: {
label: t('tryItOut'),
label: t("tryItOut"),
icon: <Icons.hand />,
},
research: {
label: t('deepDiveResearch'),
label: t("deepDiveResearch"),
icon: <Icons.readme />,
},
learn: {
label: t('learnMore'),
label: t("learnMore"),
},
}
@@ -132,18 +132,18 @@ export const WikiSideNavigation = ({
return (
<div className="sticky overflow-hidden top-20">
<aside className={cn('flex flex-col', className)}>
<aside className={cn("flex flex-col", className)}>
<h6 className="text-lg font-bold font-display text-tuatara-700">
{t('contents')}
{t("contents")}
</h6>
<ul className="pt-4 font-sans text-black text-normal">
{sections.map((section, index) => (
<li
key={index}
className={cn(
'flex h-8 items-center border-l-2 border-l-anakiwa-200 px-3 duration-200 cursor-pointer ',
"flex h-8 items-center border-l-2 border-l-anakiwa-200 px-3 duration-200 cursor-pointer ",
{
'border-l-anakiwa-500 text-anakiwa-500 font-medium':
"border-l-anakiwa-500 text-anakiwa-500 font-medium":
activeSection === section.id,
}
)}
@@ -166,9 +166,9 @@ export const WikiSideNavigation = ({
key={key}
onClick={() => scrollToSection(key)}
className={cn(
'flex h-8 items-center border-l-2 border-l-anakiwa-200 px-3 duration-200 cursor-pointer',
"flex h-8 items-center border-l-2 border-l-anakiwa-200 px-3 duration-200 cursor-pointer",
{
'border-l-anakiwa-500 text-anakiwa-500 font-medium':
"border-l-anakiwa-500 text-anakiwa-500 font-medium":
activeSection === key,
}
)}
@@ -179,12 +179,12 @@ export const WikiSideNavigation = ({
})}
<li
key="edit"
onClick={() => scrollToSection('edit-this-page')}
onClick={() => scrollToSection("edit-this-page")}
className={cn(
'flex h-8 items-center border-l-2 border-l-anakiwa-200 px-3 duration-200 cursor-pointer',
"flex h-8 items-center border-l-2 border-l-anakiwa-200 px-3 duration-200 cursor-pointer",
{
'border-l-anakiwa-500 text-anakiwa-500 font-medium':
activeSection === 'edit-this-page',
"border-l-anakiwa-500 text-anakiwa-500 font-medium":
activeSection === "edit-this-page",
}
)}
>