mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-02-06 09:55:47 +00:00
130 lines
4.4 KiB
TypeScript
130 lines
4.4 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react'
|
|
import { useLocation, useNavigate } from 'react-router-dom'
|
|
|
|
import { ContactInfoForm } from '#components/Profile/Subcomponents/ContactInfoForm'
|
|
import { CrowdfundingForm } from '#components/Profile/Subcomponents/CrowdfundingForm'
|
|
import { GalleryForm } from '#components/Profile/Subcomponents/GalleryForm'
|
|
import { GroupSubheaderForm } from '#components/Profile/Subcomponents/GroupSubheaderForm'
|
|
import { ProfileStartEndForm } from '#components/Profile/Subcomponents/ProfileStartEndForm'
|
|
import { ProfileTagsForm } from '#components/Profile/Subcomponents/ProfileTagsForm'
|
|
import { ProfileTextForm } from '#components/Profile/Subcomponents/ProfileTextForm'
|
|
|
|
import type { FormState } from '#types/FormState'
|
|
import type { Item } from '#types/Item'
|
|
import type { Key } from 'react'
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
type ComponentMap = Record<string, React.ComponentType<any>>
|
|
|
|
const componentMap: ComponentMap = {
|
|
groupSubheaders: GroupSubheaderForm,
|
|
texts: ProfileTextForm,
|
|
contactInfos: ContactInfoForm,
|
|
startEnd: ProfileStartEndForm,
|
|
crowdfundings: CrowdfundingForm,
|
|
gallery: GalleryForm,
|
|
inviteLinks: () => null,
|
|
relations: () => null, // Relations are not editable in form
|
|
tags_component: ProfileTagsForm,
|
|
attestations_component: () => null, // Attestations are view-only
|
|
}
|
|
|
|
interface TabItem {
|
|
id: string
|
|
title: string
|
|
icon?: string
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
items: { collection: string; id: Key | null | undefined; item: any }[]
|
|
}
|
|
|
|
interface Props {
|
|
item: Item
|
|
state: FormState
|
|
setState: React.Dispatch<React.SetStateAction<FormState>>
|
|
tabs: TabItem[]
|
|
iconAsLabels?: boolean
|
|
}
|
|
|
|
export const TabsContainerForm = ({ item, state, setState, tabs, iconAsLabels = false }: Props) => {
|
|
const location = useLocation()
|
|
const navigate = useNavigate()
|
|
const [activeTab, setActiveTab] = useState<number>(0)
|
|
|
|
const tabsLength = tabs.length
|
|
|
|
useEffect(() => {
|
|
if (tabs.length === 0) return
|
|
|
|
const params = new URLSearchParams(location.search)
|
|
const urlTab = params.get('tab')
|
|
if (urlTab !== null && !isNaN(Number(urlTab))) {
|
|
const index = Number(urlTab)
|
|
if (index >= 0 && index < tabs.length) {
|
|
setActiveTab(index)
|
|
}
|
|
}
|
|
}, [tabs, tabsLength, location.search])
|
|
|
|
const updateActiveTab = useCallback(
|
|
(index: number) => {
|
|
setActiveTab(index)
|
|
const params = new URLSearchParams(location.search)
|
|
params.set('tab', `${index}`)
|
|
const newUrl = location.pathname + '?' + params.toString()
|
|
navigate(newUrl)
|
|
},
|
|
[location.pathname, location.search, navigate],
|
|
)
|
|
|
|
if (tabs.length === 0) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<div className='tw:flex tw:flex-col tw:flex-1 tw:min-h-0'>
|
|
{/* Tabs */}
|
|
<div className='tw:flex tw:bg-base-200 tw:rounded-lg tw:p-1 tw:mb-4 tw:flex-none'>
|
|
{tabs.map((tab, index) => (
|
|
<button
|
|
type='button'
|
|
key={tab.id}
|
|
className={`tw:flex-1 tw:flex tw:items-center tw:justify-center tw:gap-2 tw:py-2 tw:px-4 tw:rounded-md tw:transition-colors tw:cursor-pointer ${activeTab === index ? 'tw:bg-primary tw:text-primary-content' : 'hover:tw:bg-base-300'}`}
|
|
onClick={() => updateActiveTab(index)}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault()
|
|
updateActiveTab(index)
|
|
}
|
|
}}
|
|
>
|
|
{tab.icon && <span>{tab.icon}</span>}
|
|
{!(iconAsLabels && activeTab !== index) && <span>{tab.title}</span>}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Tab Content */}
|
|
<div className='tw:flex-1 tw:flex tw:flex-col tw:min-h-0'>
|
|
{/* eslint-disable-next-line security/detect-object-injection */}
|
|
{tabs[activeTab].items.map((templateItem) => {
|
|
const TemplateComponent = componentMap[templateItem.collection]
|
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
return TemplateComponent ? (
|
|
<TemplateComponent
|
|
key={templateItem.id}
|
|
item={item}
|
|
state={state}
|
|
setState={setState}
|
|
{...templateItem.item}
|
|
/>
|
|
) : (
|
|
<div className='tw:mt-2 tw:flex-none' key={templateItem.id}>
|
|
{templateItem.collection} form not found
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|