a lot of ui fixes

This commit is contained in:
Anton Tranelis 2025-06-06 23:14:37 +02:00
parent 30069973e5
commit 758730b523
12 changed files with 208 additions and 197 deletions

View File

@ -38,7 +38,7 @@ export default function NavBar({ appName }: { appName: string }) {
<div className='tw:flex-1 tw:mr-2'> <div className='tw:flex-1 tw:mr-2'>
<div <div
className={'tw:flex-1 tw:truncate tw:grid tw:grid-flow-col'} className={'tw:flex-1 tw:truncate tw:grid tw:grid-flow-col'}
style={{ maxWidth: nameWidth + 60 }} style={{ maxWidth: nameWidth + 62 }}
> >
<Link <Link
className='tw:btn tw:btn-ghost tw:px-2 tw:normal-case tw:text-xl tw:flex-1 tw:truncate' className='tw:btn tw:btn-ghost tw:px-2 tw:normal-case tw:text-xl tw:flex-1 tw:truncate'

View File

@ -13,7 +13,6 @@ interface RichTextEditorProps {
labelTitle?: string labelTitle?: string
labelStyle?: string labelStyle?: string
containerStyle?: string containerStyle?: string
inputStyle?: string
defaultValue: string defaultValue: string
placeholder?: string placeholder?: string
required?: boolean required?: boolean

View File

@ -6,13 +6,14 @@ import H3Icon from '@heroicons/react/24/solid/H3Icon'
import ItalicIcon from '@heroicons/react/24/solid/ItalicIcon' import ItalicIcon from '@heroicons/react/24/solid/ItalicIcon'
import ListBulletIcon from '@heroicons/react/24/solid/ListBulletIcon' import ListBulletIcon from '@heroicons/react/24/solid/ListBulletIcon'
import NumberedListIcon from '@heroicons/react/24/solid/NumberedListIcon' import NumberedListIcon from '@heroicons/react/24/solid/NumberedListIcon'
import { Editor, useEditorState } from '@tiptap/react' import { useEditorState } from '@tiptap/react'
import { FaQuoteLeft } from 'react-icons/fa6' import { FaQuoteLeft } from 'react-icons/fa6'
import { MdUndo, MdRedo, MdHorizontalRule } from 'react-icons/md' import { MdUndo, MdRedo, MdHorizontalRule } from 'react-icons/md'
import { LiaTextHeightSolid } from 'react-icons/lia'
import type { Editor } from '@tiptap/react'
export const TextEditorMenu = ({ editor }: { editor: Editor }) => { export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
const editorState = useEditorState({ const editorState = useEditorState({
editor, editor,
selector: (ctx) => { selector: (ctx) => {
@ -57,10 +58,10 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
return ( return (
<> <>
<ul className='tw:menu tw:menu-horizontal tw:flex-none tw:bg-base-200 tw:rounded-box tw:w-full tw:rounded-b-none'> <ul className='tw:menu tw:p-1 tw:menu-horizontal tw:flex-none tw:bg-base-200 tw:rounded-box tw:w-full tw:rounded-b-none'>
<li> <li>
<div <div
className={`tw:tooltip ${editorState.canUndo ? '' : 'tw:opacity-50'}`} className={`tw:tooltip tw:px-1 ${editorState.canUndo ? '' : 'tw:opacity-50'}`}
data-tip='Undo' data-tip='Undo'
onClick={() => editor.chain().focus().undo().run()} onClick={() => editor.chain().focus().undo().run()}
> >
@ -69,7 +70,7 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
</li> </li>
<li> <li>
<div <div
className={`tw:tooltip ${editorState.canRedo ? '' : 'tw:opacity-50'}`} className={`tw:tooltip tw:px-1 ${editorState.canRedo ? '' : 'tw:opacity-50'}`}
data-tip='Redo' data-tip='Redo'
onClick={() => editor.chain().focus().redo().run()} onClick={() => editor.chain().focus().redo().run()}
> >
@ -78,7 +79,7 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
</li> </li>
<li> <li>
<div <div
className={`tw:tooltip ${editorState.isBold ? 'tw:bg-base-content/10' : ''}`} className={`tw:tooltip tw:px-1 ${editorState.isBold ? 'tw:bg-base-content/10' : ''}`}
data-tip='Bold' data-tip='Bold'
onClick={() => editor.chain().focus().toggleBold().run()} onClick={() => editor.chain().focus().toggleBold().run()}
> >
@ -87,7 +88,7 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
</li> </li>
<li> <li>
<div <div
className={`tw:tooltip ${editorState.isItalic ? 'tw:bg-base-content/10' : ''}`} className={`tw:tooltip tw:px-1 ${editorState.isItalic ? 'tw:bg-base-content/10' : ''}`}
data-tip='Italic' data-tip='Italic'
onClick={() => editor.chain().focus().toggleItalic().run()} onClick={() => editor.chain().focus().toggleItalic().run()}
> >
@ -95,35 +96,45 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
</div> </div>
</li> </li>
<li> <li>
<div <details className='tw:z-10000'>
className={`tw:tooltip ${editorState.isHeading1 ? 'tw:bg-base-content/10' : ''}`} <summary>
data-tip='Heading 1' <LiaTextHeightSolid className='tw:w-5 tw:h-5' />
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} </summary>
> <ul>
<H1Icon className='tw:w-5 tw:h-5' /> <li>
</div> <div
className={`tw:tooltip tw:px-1 ${editorState.isHeading1 ? 'tw:bg-base-content/10' : ''}`}
data-tip='Heading 1'
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
>
<H1Icon className='tw:w-5 tw:h-5' />
</div>
</li>
<li>
<div
className={`tw:tooltip tw:px-1 ${editorState.isHeading2 ? 'tw:bg-base-content/10' : ''}`}
data-tip='Heading 2'
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
>
<H2Icon className='tw:w-5 tw:h-5' />
</div>
</li>
<li>
<div
className={`tw:tooltip tw:px-1 ${editorState.isHeading3 ? 'tw:bg-base-content/10' : ''}`}
data-tip='Heading 3'
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
>
<H3Icon className='tw:w-5 tw:h-5' />
</div>
</li>
</ul>
</details>
</li> </li>
<li> <li>
<div <div
className={`tw:tooltip ${editorState.isHeading2 ? 'tw:bg-base-content/10' : ''}`} className={`tw:tooltip tw:px-1 ${editorState.isBulletList ? 'tw:bg-base-content/10' : ''}`}
data-tip='Heading 2'
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
>
<H2Icon className='tw:w-5 tw:h-5' />
</div>
</li>
<li>
<div
className={`tw:tooltip ${editorState.isHeading3 ? 'tw:bg-base-content/10' : ''}`}
data-tip='Heading 3'
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
>
<H3Icon className='tw:w-5 tw:h-5' />
</div>
</li>
<li>
<div
className={`tw:tooltip ${editorState.isBulletList ? 'tw:bg-base-content/10' : ''}`}
data-tip='List' data-tip='List'
onClick={() => editor.chain().focus().toggleBulletList().run()} onClick={() => editor.chain().focus().toggleBulletList().run()}
> >
@ -132,7 +143,7 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
</li> </li>
<li> <li>
<div <div
className={`tw:tooltip ${editorState.isOrderedList ? 'tw:bg-base-content/10' : ''}`} className={`tw:tooltip tw:px-1 ${editorState.isOrderedList ? 'tw:bg-base-content/10' : ''}`}
data-tip='List' data-tip='List'
onClick={() => editor.chain().focus().toggleOrderedList().run()} onClick={() => editor.chain().focus().toggleOrderedList().run()}
> >
@ -141,16 +152,7 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
</li> </li>
<li> <li>
<div <div
className={`tw:tooltip ${editorState.isCodeBlock ? 'tw:bg-base-content/10' : ''}`} className={`tw:tooltip tw:px-1 ${editorState.isBlockquote ? 'tw:bg-base-content/10' : ''}`}
data-tip='Code'
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
>
<CodeBracketIcon className='tw:w-5 tw:h-5' />
</div>
</li>
<li>
<div
className={`tw:tooltip ${editorState.isBlockquote ? 'tw:bg-base-content/10' : ''}`}
data-tip='Quote' data-tip='Quote'
onClick={() => editor.chain().focus().toggleBlockquote().run()} onClick={() => editor.chain().focus().toggleBlockquote().run()}
> >
@ -159,7 +161,7 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
</li> </li>
<li> <li>
<div <div
className='tw:tooltip' className='tw:tooltip tw:px-1'
data-tip='Horizontal Line' data-tip='Horizontal Line'
onClick={() => editor.chain().focus().setHorizontalRule().run()} onClick={() => editor.chain().focus().setHorizontalRule().run()}
> >

View File

@ -26,7 +26,7 @@ export const PopupStartEndInput = ({
placeholder='start' placeholder='start'
dataField='start' dataField='start'
inputStyle='tw:text-sm tw:px-2' inputStyle='tw:text-sm tw:px-2'
labelTitle={showLabels ? 'start' : ''} labelTitle={showLabels ? 'Start' : ''}
defaultValue={item && item.start ? item.start.substring(0, 10) : ''} defaultValue={item && item.start ? item.start.substring(0, 10) : ''}
autocomplete='one-time-code' autocomplete='one-time-code'
updateFormValue={updateStartValue} updateFormValue={updateStartValue}
@ -36,7 +36,7 @@ export const PopupStartEndInput = ({
placeholder='end' placeholder='end'
dataField='end' dataField='end'
inputStyle='tw:text-sm tw:px-2' inputStyle='tw:text-sm tw:px-2'
labelTitle={showLabels ? 'end' : ''} labelTitle={showLabels ? 'End' : ''}
defaultValue={item && item.end ? item.end.substring(0, 10) : ''} defaultValue={item && item.end ? item.end.substring(0, 10) : ''}
autocomplete='one-time-code' autocomplete='one-time-code'
updateFormValue={updateEndValue} updateFormValue={updateEndValue}

View File

@ -23,7 +23,7 @@ export const PopupTextInput = ({
placeholder={placeholder} placeholder={placeholder}
inputStyle={style} inputStyle={style}
type='text' type='text'
containerStyle={'tw:mt-4'} containerStyle={'tw:mt-4 tw:mb-2'}
></TextInput> ></TextInput>
) )
} }

View File

@ -158,7 +158,7 @@ export function ProfileForm() {
<> <>
<MapOverlayPage <MapOverlayPage
backdrop backdrop
className='tw:mx-4 tw:mt-4 tw:mb-4 tw:overflow-x-hidden tw:w-[calc(100%-32px)] tw:md:w-[calc(50%-32px)] tw:max-w-3xl tw:left-auto! tw:top-0 tw:bottom-0 tw:flex tw:flex-col tw:max-h-[calc(1200px)]' className='tw:mx-4 tw:mt-4 tw:mb-4 tw:overflow-x-hidden tw:w-[calc(100%-32px)] tw:md:w-[calc(50%-32px)] tw:max-w-3xl tw:left-auto! tw:top-0 tw:bottom-0 tw:flex tw:flex-col tw:max-h-[calc(1000px)]'
> >
<form <form
className='tw:flex tw:flex-col tw:flex-1 tw:min-h-0' className='tw:flex tw:flex-col tw:flex-1 tw:min-h-0'
@ -204,7 +204,7 @@ export function ProfileForm() {
></TabsForm> ></TabsForm>
)} )}
<div className='tw:mb-4 tw:mt-6 tw:flex-none'> <div className='tw:mt-6 tw:flex-none'>
<button <button
className={`${loading ? ' tw:loading tw:btn tw:float-right' : 'tw:btn tw:float-right'}`} className={`${loading ? ' tw:loading tw:btn tw:float-right' : 'tw:btn tw:float-right'}`}
type='submit' type='submit'

View File

@ -174,7 +174,7 @@ export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any>
{item && ( {item && (
<MapOverlayPage <MapOverlayPage
key={item.id} key={item.id}
className={`tw:p-0! tw:overflow-scroll tw:m-4! tw:md:w-[calc(50%-32px)] tw:w-[calc(100%-32px)] tw:min-w-80 tw:max-w-3xl tw:left-0! tw:sm:left-auto! tw:top-0 tw:bottom-0 tw:transition-opacity tw:duration-500 ${!selectPosition ? 'tw:opacity-100 tw:pointer-events-auto' : 'tw:opacity-0 tw:pointer-events-none'}`} className={`tw:p-0! tw:overflow-scroll tw:m-4! tw:md:w-[calc(50%-32px)] tw:w-[calc(100%-32px)] tw:min-w-80 tw:max-w-3xl tw:left-0! tw:sm:left-auto! tw:top-0 tw:bottom-0 tw:transition-opacity tw:duration-500 ${!selectPosition ? 'tw:opacity-100 tw:pointer-events-auto' : 'tw:opacity-0 tw:pointer-events-none'} tw:max-h-[1000px]`}
> >
<> <>
<div className={'tw:px-6 tw:pt-6'}> <div className={'tw:px-6 tw:pt-6'}>

View File

@ -38,7 +38,7 @@ export const ProfileTextForm = ({
return ( return (
<div <div
className={`tw:min-h-32 tw:flex tw:flex-col tw:mt-2 ${size === 'full' ? 'tw:flex-1' : 'tw:h-32 tw:flex-none'}`} className={`tw:min-h-36 tw:max-h-156 tw:flex tw:flex-col tw:mt-2 ${size === 'full' ? 'tw:flex-1' : 'tw:h-36 tw:flex-none'}`}
> >
<div className='tw:flex tw:justify-between tw:items-center'> <div className='tw:flex tw:justify-between tw:items-center'>
<label <label
@ -60,7 +60,7 @@ export const ProfileTextForm = ({
})) }))
} }
labelStyle={hideInputLabel ? 'tw:hidden' : ''} labelStyle={hideInputLabel ? 'tw:hidden' : ''}
containerStyle={size === 'full' ? 'tw:flex-1' : 'tw:h-28 tw:flex-none'} containerStyle={size === 'full' ? 'tw:flex-1' : 'tw:h-32 tw:flex-none'}
required={required} required={required}
/> />
</div> </div>

View File

@ -33,6 +33,9 @@ export const TagsWidget = ({ placeholder, containerStyle, defaultTags, onUpdate
} }
const onKeyDown = (e) => { const onKeyDown = (e) => {
if (e.key === 'Enter') {
e.preventDefault()
}
const { key } = e const { key } = e
const trimmedInput = input.trim() const trimmedInput = input.trim()
@ -42,7 +45,6 @@ export const TagsWidget = ({ placeholder, containerStyle, defaultTags, onUpdate
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types
!defaultTags.some((tag) => tag.name.toLocaleLowerCase() === trimmedInput.toLocaleLowerCase()) !defaultTags.some((tag) => tag.name.toLocaleLowerCase() === trimmedInput.toLocaleLowerCase())
) { ) {
e.preventDefault()
const newTag = tags.find((t) => t.name === trimmedInput.toLocaleLowerCase()) const newTag = tags.find((t) => t.name === trimmedInput.toLocaleLowerCase())
newTag && onUpdate([...currentTags, newTag]) newTag && onUpdate([...currentTags, newTag])
!newTag && !newTag &&

View File

@ -6,7 +6,6 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */ /* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
import { useCallback, useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { RichTextEditor } from '#components/Input/RichTextEditor' import { RichTextEditor } from '#components/Input/RichTextEditor'
@ -15,6 +14,7 @@ import { PopupStartEndInput, TextView } from '#components/Map/Subcomponents/Item
import { ActionButton } from '#components/Profile/Subcomponents/ActionsButton' import { ActionButton } from '#components/Profile/Subcomponents/ActionsButton'
import { LinkedItemsHeaderView } from '#components/Profile/Subcomponents/LinkedItemsHeaderView' import { LinkedItemsHeaderView } from '#components/Profile/Subcomponents/LinkedItemsHeaderView'
import { TagsWidget } from '#components/Profile/Subcomponents/TagsWidget' import { TagsWidget } from '#components/Profile/Subcomponents/TagsWidget'
import { Tabs } from '#components/Templates/Tabs'
export const TabsForm = ({ export const TabsForm = ({
item, item,
@ -26,114 +26,70 @@ export const TabsForm = ({
loading, loading,
setUrlParams, setUrlParams,
}) => { }) => {
const [activeTab, setActiveTab] = useState<number>(1)
const navigate = useNavigate()
const updateItem = useUpdateItem() const updateItem = useUpdateItem()
const navigate = useNavigate()
const updateActiveTab = useCallback(
(id: number) => {
setActiveTab(id)
const params = new URLSearchParams(window.location.search)
params.set('tab', `${id}`)
const newUrl = location.pathname + '?' + params.toString()
window.history.pushState({}, '', newUrl)
setUrlParams(params)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[location.pathname],
)
useEffect(() => {
const params = new URLSearchParams(location.search)
const urlTab = params.get('tab')
setActiveTab(urlTab ? Number(urlTab) : 1)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location.search])
return ( return (
<div className='tw:grow tw:flex tw:flex-col tw:min-h-0'> <div className='tw:flex tw:flex-col tw:flex-1 tw:min-h-0 tw:mt-4'>
<div role='tablist' className='tw:tabs tw:flex tw:flex-1 tw:min-h-0 tw:tabs-lift tw:mt-3'> <Tabs
<input setUrlParams={setUrlParams}
type='radio' items={[
name='my_tabs_2' {
role='tab' title: 'Info',
className={'tw:tab'} component: (
aria-label='Info' <div
checked={activeTab === 1 && true} className={`tw:flex tw:flex-col tw:flex-1 tw:min-h-0 ${item.layer.itemType.show_start_end_input && 'tw:pt-4'}`}
onChange={() => updateActiveTab(1)} >
/> {item.layer.itemType.show_start_end_input && (
<div <PopupStartEndInput
role='tabpanel' item={item}
className='tw:!flex tw:!flex-col tw:tab-content tw:bg-base-100 tw:border-(--fallback-bc,oklch(var(--bc)/0.2)) tw:rounded-box tw:!h-[calc(100%-48px)] tw:min-h-56 tw:border-none' showLabels={true}
> updateEndValue={(e) =>
<div setState((prevState) => ({
className={`tw:flex tw:flex-col tw:flex-1 tw:min-h-0 ${item.layer.itemType.show_start_end_input && 'tw:pt-4'}`} ...prevState,
> end: e,
{item.layer.itemType.show_start_end_input && ( }))
<PopupStartEndInput }
item={item} updateStartValue={(s) =>
showLabels={false} setState((prevState) => ({
updateEndValue={(e) => ...prevState,
setState((prevState) => ({ start: s,
...prevState, }))
end: e, }
})) ></PopupStartEndInput>
} )}
updateStartValue={(s) =>
setState((prevState) => ({
...prevState,
start: s,
}))
}
></PopupStartEndInput>
)}
<RichTextEditor <RichTextEditor
labelTitle='About me' labelTitle='About'
placeholder='about ...' placeholder='about ...'
defaultValue={item?.text ? item.text : ''} defaultValue={item?.text ? item.text : ''}
updateFormValue={(v) => updateFormValue={(v) =>
setState((prevState) => ({ setState((prevState) => ({
...prevState, ...prevState,
text: v, text: v,
})) }))
} }
containerStyle='tw:pt-2 tw:grow' containerStyle='tw:pt-2 tw:flex-1 tw:min-h-36 tw:max-h-136'
inputStyle={`tw:h-full ${!item.layer.itemType.show_start_end_input && 'tw:border-t-0 tw:rounded-tl-none'}`} />
/> <RichTextEditor
<RichTextEditor labelTitle='Contact Info'
labelTitle='Contact Info' placeholder='contact info ...'
placeholder='contact info ...' defaultValue={state.contact || ''}
defaultValue={state.contact || ''} updateFormValue={(c) =>
updateFormValue={(c) => setState((prevState) => ({
setState((prevState) => ({ ...prevState,
...prevState, contact: c,
contact: c, }))
})) }
} containerStyle='tw:pt-2 tw:h-36 tw:flex-none'
inputStyle='' required={false}
containerStyle='tw:pt-2 tw:h-36 tw:flex-none' />
required={false} </div>
/> ),
</div> },
</div> {
{item.layer?.itemType.offers_and_needs && ( title: 'Offers & Needs',
<> component: (
<input
type='radio'
name='my_tabs_2'
role='tab'
className={'tw:tab tw:min-w-[10em] '}
aria-label='Offers & Needs'
checked={activeTab === 3 && true}
onChange={() => updateActiveTab(3)}
/>
<div
role='tabpanel'
className='tw:tab-content tw:bg-base-100 tw:border-(--fallback-bc,oklch(var(--bc)/0.2)) tw:rounded-box tw:!h-[calc(100%-48px)] tw:min-h-56 tw:border-none'
>
<div className='tw:h-full'> <div className='tw:h-full'>
<div className='tw:w-full tw:h-[calc(50%-0.75em)] tw:mb-4'> <div className='tw:w-full tw:h-[calc(50%-0.75em)] tw:mb-4'>
<TagsWidget <TagsWidget
@ -162,24 +118,11 @@ export const TabsForm = ({
/> />
</div> </div>
</div> </div>
</div> ),
</> },
)} {
{item.layer?.itemType.relations && ( title: 'Links',
<> component: (
<input
type='radio'
name='my_tabs_2'
role='tab'
className='tw:tab '
aria-label='Links'
checked={activeTab === 7 && true}
onChange={() => updateActiveTab(7)}
/>
<div
role='tabpanel'
className='tw:tab-content tw:rounded-box tw:!h-[calc(100%-48px)] tw:overflow-y-auto tw:pt-4 tw:overflow-x-hidden fade'
>
<div className='tw:h-full'> <div className='tw:h-full'>
<div className='tw:grid tw:grid-cols-1 tw:sm:grid-cols-2 tw:md:grid-cols-1 tw:lg:grid-cols-1 tw:xl:grid-cols-1 tw:2xl:grid-cols-2 tw:mb-4'> <div className='tw:grid tw:grid-cols-1 tw:sm:grid-cols-2 tw:md:grid-cols-1 tw:lg:grid-cols-1 tw:xl:grid-cols-1 tw:2xl:grid-cols-2 tw:mb-4'>
{state.relations && {state.relations &&
@ -211,10 +154,10 @@ export const TabsForm = ({
)} )}
</div> </div>
</div> </div>
</div> ),
</> },
)} ]}
</div> ></Tabs>
</div> </div>
) )
} }

View File

@ -91,9 +91,9 @@ export const TabsView = ({
name='my_tabs_2' name='my_tabs_2'
role='tab' role='tab'
className={'tw:tab tw:font-bold tw:ps-2! tw:pe-2! '} className={'tw:tab tw:font-bold tw:ps-2! tw:pe-2! '}
aria-label={`${item.layer?.itemType.icon_as_labels && activeTab !== 1 ? '📝' : '📝\u00A0Info'}`} aria-label={`${item.layer?.itemType.icon_as_labels && activeTab !== 0 ? '📝' : '📝\u00A0Info'}`}
checked={activeTab === 1 && true} checked={activeTab === 0 && true}
onChange={() => updateActiveTab(1)} onChange={() => updateActiveTab(0)}
/> />
<div <div
role='tabpanel' role='tabpanel'
@ -115,9 +115,9 @@ export const TabsView = ({
name='my_tabs_2' name='my_tabs_2'
role='tab' role='tab'
className={'tw:tab tw:font-bold tw:ps-2! tw:pe-2!'} className={'tw:tab tw:font-bold tw:ps-2! tw:pe-2!'}
aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 2 ? '❤️' : '❤️\u00A0Trust'}`} aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 3 ? '❤️' : '❤️\u00A0Trust'}`}
checked={activeTab === 2 && true} checked={activeTab === 3 && true}
onChange={() => updateActiveTab(2)} onChange={() => updateActiveTab(3)}
/> />
<div <div
role='tabpanel' role='tabpanel'
@ -197,10 +197,10 @@ export const TabsView = ({
type='radio' type='radio'
name='my_tabs_2' name='my_tabs_2'
role='tab' role='tab'
className={`tw:tab tw:font-bold tw:ps-2! tw:pe-2! ${!(item.layer.itemType.icon_as_labels && activeTab !== 3) && 'tw:min-w-[10.4em]'} `} className={`tw:tab tw:font-bold tw:ps-2! tw:pe-2! ${!(item.layer.itemType.icon_as_labels && activeTab !== 1) && 'tw:min-w-[10.4em]'} `}
aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 3 ? '♻️' : '♻️\u00A0Offers & Needs'}`} aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 1 ? '♻️' : '♻️\u00A0Offers & Needs'}`}
checked={activeTab === 3 && true} checked={activeTab === 1 && true}
onChange={() => updateActiveTab(3)} onChange={() => updateActiveTab(1)}
/> />
<div <div
role='tabpanel' role='tabpanel'
@ -251,9 +251,9 @@ export const TabsView = ({
name='my_tabs_2' name='my_tabs_2'
role='tab' role='tab'
className='tw:tab tw:font-bold tw:ps-2! tw:pe-2! ' className='tw:tab tw:font-bold tw:ps-2! tw:pe-2! '
aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 7 ? '🔗' : '🔗\u00A0Links'}`} aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 2 ? '🔗' : '🔗\u00A0Links'}`}
checked={activeTab === 7 && true} checked={activeTab === 2 && true}
onChange={() => updateActiveTab(7)} onChange={() => updateActiveTab(2)}
/> />
<div <div
role='tabpanel' role='tabpanel'

View File

@ -0,0 +1,65 @@
/* eslint-disable security/detect-object-injection */
import { useState, useEffect, useCallback } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
interface TabItem {
title: string
component: React.ReactNode
}
interface TabsProps {
items: TabItem[]
setUrlParams: (params: URLSearchParams) => void
}
export const Tabs: React.FC<TabsProps> = ({ items, setUrlParams }: TabsProps) => {
const location = useLocation()
const navigate = useNavigate()
const [activeIndex, setActiveIndex] = useState<number>(0)
useEffect(() => {
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 < items.length) {
setActiveIndex(index)
}
}
}, [items.length, location.search])
const updateActiveTab = useCallback(
(index: number) => {
setActiveIndex(index)
const params = new URLSearchParams(location.search)
params.set('tab', `${index}`)
setUrlParams(params)
const newUrl = location.pathname + '?' + params.toString()
navigate(newUrl, { replace: false })
},
[location, navigate],
)
return (
<div className='tw:flex tw:flex-col tw:flex-1 tw:min-h-0'>
<div role='tablist' className='tw:tabs tw:tabs-lift tw:flex-none'>
{items.map((item, index) => (
<div
key={index}
role='tab'
className={`tw:tab ${index === activeIndex ? 'tw:tab-active' : ''}`}
onClick={() => updateActiveTab(index)}
>
{item.title}
</div>
))}
</div>
<div className='tw:flex-1 tw:flex tw:flex-col tw:min-h-0'>
{items[activeIndex]?.component}
</div>
</div>
)
}