mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-02-06 09:55:47 +00:00
linting
This commit is contained in:
parent
5d3a24fb64
commit
24f10e2b0a
@ -1,78 +1,93 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { TagView } from '#components/Templates/TagView'
|
||||
|
||||
import type { Tag } from '#types/Tag'
|
||||
import type { ChangeEvent, KeyboardEvent } from 'react'
|
||||
|
||||
interface InputProps {
|
||||
value: string
|
||||
placeholder?: string
|
||||
onKeyDown: (e: KeyboardEvent<HTMLInputElement>) => void
|
||||
onKeyUp: () => void
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
interface AutocompleteProps {
|
||||
inputProps: InputProps
|
||||
suggestions: Tag[]
|
||||
onSelected: (suggestion: Tag) => void
|
||||
pushFilteredSuggestions?: Tag[]
|
||||
setFocus?: boolean
|
||||
}
|
||||
|
||||
export const Autocomplete = ({
|
||||
inputProps,
|
||||
suggestions,
|
||||
onSelected,
|
||||
pushFilteredSuggestions,
|
||||
setFocus,
|
||||
}: {
|
||||
inputProps: any
|
||||
suggestions: any[]
|
||||
onSelected: (suggestion) => void
|
||||
pushFilteredSuggestions?: any[]
|
||||
setFocus?: boolean
|
||||
}) => {
|
||||
const [filteredSuggestions, setFilteredSuggestions] = useState<any[]>([])
|
||||
const [heighlightedSuggestion, setHeighlightedSuggestion] = useState<number>(0)
|
||||
}: AutocompleteProps) => {
|
||||
const [filteredSuggestions, setFilteredSuggestions] = useState<Tag[]>([])
|
||||
const [highlightedSuggestion, setHighlightedSuggestion] = useState<number>(0)
|
||||
|
||||
useEffect(() => {
|
||||
pushFilteredSuggestions && setFilteredSuggestions(pushFilteredSuggestions)
|
||||
if (pushFilteredSuggestions) {
|
||||
setFilteredSuggestions(pushFilteredSuggestions)
|
||||
}
|
||||
}, [pushFilteredSuggestions])
|
||||
|
||||
useEffect(() => {
|
||||
setFocus && inputRef.current?.focus()
|
||||
if (setFocus) {
|
||||
inputRef.current?.focus()
|
||||
}
|
||||
}, [setFocus])
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>()
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const getSuggestions = (value) => {
|
||||
const getSuggestions = (value: string): Tag[] => {
|
||||
const inputValue = value.trim().toLowerCase()
|
||||
const inputLength = inputValue.length
|
||||
|
||||
return inputLength === 0
|
||||
? []
|
||||
: suggestions.filter((tag) => tag.name.toLowerCase().slice(0, inputLength) === inputValue)
|
||||
: suggestions.filter((tag) => tag.name.toLowerCase().startsWith(inputValue))
|
||||
}
|
||||
|
||||
const handleChange = (e) => {
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setFilteredSuggestions(getSuggestions(e.target.value))
|
||||
|
||||
// Call the parent's onChange handler, if it exists
|
||||
if (inputProps.onChange) {
|
||||
inputProps.onChange(e)
|
||||
}
|
||||
// Call the parent's onChange handler
|
||||
inputProps.onChange(e)
|
||||
}
|
||||
|
||||
function handleSuggestionClick(suggestion) {
|
||||
function handleSuggestionClick(suggestion: Tag) {
|
||||
onSelected(suggestion)
|
||||
}
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
heighlightedSuggestion < filteredSuggestions.length - 1 &&
|
||||
setHeighlightedSuggestion((current) => current + 1)
|
||||
if (highlightedSuggestion < filteredSuggestions.length - 1) {
|
||||
setHighlightedSuggestion((current) => current + 1)
|
||||
}
|
||||
break
|
||||
case 'ArrowUp':
|
||||
heighlightedSuggestion > 0 && setHeighlightedSuggestion((current) => current - 1)
|
||||
if (highlightedSuggestion > 0) {
|
||||
setHighlightedSuggestion((current) => current - 1)
|
||||
}
|
||||
break
|
||||
case 'Enter':
|
||||
event.preventDefault()
|
||||
if (filteredSuggestions.length > 0) {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
onSelected(filteredSuggestions[heighlightedSuggestion])
|
||||
setHeighlightedSuggestion(0)
|
||||
onSelected(filteredSuggestions[highlightedSuggestion])
|
||||
setHighlightedSuggestion(0)
|
||||
}
|
||||
if (filteredSuggestions.length === 0) {
|
||||
inputProps.onKeyDown(event)
|
||||
}
|
||||
filteredSuggestions.length === 0 && inputProps.onKeyDown(event)
|
||||
break
|
||||
default:
|
||||
inputProps.onKeyDown(event)
|
||||
@ -87,16 +102,16 @@ export const Autocomplete = ({
|
||||
{...inputProps}
|
||||
type='text'
|
||||
onChange={(e) => handleChange(e)}
|
||||
tabIndex='-1'
|
||||
tabIndex={-1}
|
||||
onKeyDown={handleKeyDown}
|
||||
className='tw:border-none tw:focus:outline-none tw:focus:ring-0 tw:mt-5 tw:w-full'
|
||||
/>
|
||||
<ul
|
||||
className={`tw:absolute tw:z-4000 ${filteredSuggestions.length > 0 && 'tw:bg-base-100 tw:rounded-xl tw:p-2'}`}
|
||||
className={`tw:absolute tw:z-4000 ${filteredSuggestions.length > 0 ? 'tw:bg-base-100 tw:rounded-xl tw:p-2' : ''}`}
|
||||
>
|
||||
{filteredSuggestions.map((suggestion, index) => (
|
||||
<li key={index} onClick={() => handleSuggestionClick(suggestion)}>
|
||||
<TagView heighlight={index === heighlightedSuggestion} tag={suggestion}></TagView>
|
||||
<li key={suggestion.id} onClick={() => handleSuggestionClick(suggestion)}>
|
||||
<TagView heighlight={index === highlightedSuggestion} tag={suggestion}></TagView>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@ -21,8 +21,8 @@ import { useTags } from '#components/Map/hooks/useTags'
|
||||
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
|
||||
import { MapOverlayPage } from '#components/Templates'
|
||||
|
||||
import { handleDelete, linkItem, unlinkItem } from './itemFunctions'
|
||||
import { AttestationsContext } from './hooks/useAttestations'
|
||||
import { handleDelete, linkItem, unlinkItem } from './itemFunctions'
|
||||
import { FlexView } from './Templates/FlexView'
|
||||
import { OnepagerView } from './Templates/OnepagerView'
|
||||
import { SimpleView } from './Templates/SimpleView'
|
||||
|
||||
@ -53,13 +53,16 @@ export const AttestationsView = ({ item, heading = 'Trust', hideWhenEmpty = true
|
||||
</td>
|
||||
<td>
|
||||
{getUserProfile(a.user_created.id) ? (
|
||||
<Link to={'/item/' + getUserProfile(a.user_created.id)?.id}>
|
||||
<Link to={'/item/' + (getUserProfile(a.user_created.id)?.id ?? '')}>
|
||||
<div className='flex items-center gap-3'>
|
||||
<div className='tw:avatar'>
|
||||
<div className='tw:mask tw:rounded-full tw:h-8 tw:w-8 tw:mr-2'>
|
||||
{getUserProfile(a.user_created.id)?.image && (
|
||||
<img
|
||||
src={appState.assetsApi.url + getUserProfile(a.user_created.id)?.image}
|
||||
src={
|
||||
appState.assetsApi.url +
|
||||
(getUserProfile(a.user_created.id)?.image ?? '')
|
||||
}
|
||||
alt='Avatar'
|
||||
/>
|
||||
)}
|
||||
@ -67,7 +70,7 @@ export const AttestationsView = ({ item, heading = 'Trust', hideWhenEmpty = true
|
||||
</div>
|
||||
<div>
|
||||
<div className='font-bold'>
|
||||
{getUserProfile(a.user_created.id)?.name ?? a.user_created.first_name}{' '}
|
||||
{getUserProfile(a.user_created.id)?.name ?? a.user_created.first_name}
|
||||
</div>
|
||||
<div className='tw:text-xs opacity-50 tw:text-zinc-500'>
|
||||
{timeAgo(a.date_created)}
|
||||
|
||||
@ -12,26 +12,24 @@ interface Props {
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
export const ProfileTagsForm = ({
|
||||
state,
|
||||
setState,
|
||||
dataField,
|
||||
heading,
|
||||
placeholder,
|
||||
}: Props) => {
|
||||
export const ProfileTagsForm = ({ state, setState, dataField, heading, placeholder }: Props) => {
|
||||
const defaultHeading = dataField === 'offers' ? 'Offers' : 'Needs'
|
||||
const defaultPlaceholder = dataField === 'offers' ? 'enter your offers' : 'enter your needs'
|
||||
|
||||
return (
|
||||
<div className='tw:flex-1 tw:flex tw:flex-col tw:min-h-0'>
|
||||
<h3 className='tw:text-base tw:font-semibold tw:mt-4 tw:mb-2 tw:flex-none'>{heading ?? defaultHeading}</h3>
|
||||
<h3 className='tw:text-base tw:font-semibold tw:mt-4 tw:mb-2 tw:flex-none'>
|
||||
{heading ?? defaultHeading}
|
||||
</h3>
|
||||
<TagsWidget
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
defaultTags={state[dataField]}
|
||||
onUpdate={(tags) =>
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
[dataField]: tags,
|
||||
}))
|
||||
setState((prevState) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const updated = { ...prevState, [dataField]: tags }
|
||||
return updated
|
||||
})
|
||||
}
|
||||
placeholder={placeholder ?? defaultPlaceholder}
|
||||
containerStyle='tw:bg-transparent tw:w-full tw:flex-1 tw:text-xs tw:pb-2 tw:overflow-auto'
|
||||
|
||||
@ -12,16 +12,12 @@ interface Props {
|
||||
hideWhenEmpty?: boolean
|
||||
}
|
||||
|
||||
export const ProfileTagsView = ({
|
||||
item,
|
||||
dataField,
|
||||
heading,
|
||||
hideWhenEmpty = true,
|
||||
}: Props) => {
|
||||
export const ProfileTagsView = ({ item, dataField, heading, hideWhenEmpty = true }: Props) => {
|
||||
const addFilterTag = useAddFilterTag()
|
||||
const allTags = useTags()
|
||||
|
||||
// Get the tag IDs from the item based on dataField
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
const tagRelations = item[dataField] ?? []
|
||||
|
||||
// Resolve tag IDs to full Tag objects
|
||||
|
||||
@ -6,8 +6,8 @@ import { CrowdfundingForm } from '#components/Profile/Subcomponents/Crowdfunding
|
||||
import { GalleryForm } from '#components/Profile/Subcomponents/GalleryForm'
|
||||
import { GroupSubheaderForm } from '#components/Profile/Subcomponents/GroupSubheaderForm'
|
||||
import { ProfileStartEndForm } from '#components/Profile/Subcomponents/ProfileStartEndForm'
|
||||
import { ProfileTextForm } from '#components/Profile/Subcomponents/ProfileTextForm'
|
||||
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'
|
||||
@ -34,7 +34,7 @@ interface TabItem {
|
||||
title: string
|
||||
icon?: string
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
items: Array<{ collection: string; id: Key | null | undefined; item: any }>
|
||||
items: { collection: string; id: Key | null | undefined; item: any }[]
|
||||
}
|
||||
|
||||
interface Props {
|
||||
@ -42,24 +42,18 @@ interface Props {
|
||||
state: FormState
|
||||
setState: React.Dispatch<React.SetStateAction<FormState>>
|
||||
tabs: TabItem[]
|
||||
icon_as_labels?: boolean
|
||||
iconAsLabels?: boolean
|
||||
}
|
||||
|
||||
export const TabsContainerForm = ({
|
||||
item,
|
||||
state,
|
||||
setState,
|
||||
tabs,
|
||||
icon_as_labels = false,
|
||||
}: Props) => {
|
||||
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 ?? 0
|
||||
const tabsLength = tabs.length
|
||||
|
||||
useEffect(() => {
|
||||
if (!tabs || tabs.length === 0) return
|
||||
if (tabs.length === 0) return
|
||||
|
||||
const params = new URLSearchParams(location.search)
|
||||
const urlTab = params.get('tab')
|
||||
@ -82,7 +76,7 @@ export const TabsContainerForm = ({
|
||||
[location.pathname, location.search, navigate],
|
||||
)
|
||||
|
||||
if (!tabs || tabs.length === 0) {
|
||||
if (tabs.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
@ -92,7 +86,7 @@ export const TabsContainerForm = ({
|
||||
<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"
|
||||
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)}
|
||||
@ -104,15 +98,17 @@ export const TabsContainerForm = ({
|
||||
}}
|
||||
>
|
||||
{tab.icon && <span>{tab.icon}</span>}
|
||||
{!(icon_as_labels && activeTab !== index) && <span>{tab.title}</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'>
|
||||
{tabs[activeTab]?.items.map((templateItem) => {
|
||||
{/* 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}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
/* eslint-disable camelcase */
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
import { AttestationsView } from '#components/Profile/Subcomponents/AttestationsView'
|
||||
@ -43,10 +42,10 @@ interface TabItem {
|
||||
interface Props {
|
||||
item: Item
|
||||
tabs: TabItem[]
|
||||
icon_as_labels?: boolean
|
||||
iconAsLabels?: boolean
|
||||
}
|
||||
|
||||
export const TabsContainerView = ({ item, tabs = [], icon_as_labels = false }: Props) => {
|
||||
export const TabsContainerView = ({ item, tabs = [], iconAsLabels = false }: Props) => {
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const [activeTab, setActiveTab] = useState<number>(0)
|
||||
@ -92,15 +91,17 @@ export const TabsContainerView = ({ item, tabs = [], icon_as_labels = false }: P
|
||||
onClick={() => updateActiveTab(index)}
|
||||
>
|
||||
{tab.icon && <span>{tab.icon}</span>}
|
||||
{!(icon_as_labels && activeTab !== index) && <span>{tab.title}</span>}
|
||||
{!(iconAsLabels && activeTab !== index) && <span>{tab.title}</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
<div className='tw:overflow-y-auto fade tw:pb-4 tw:overflow-x-hidden'>
|
||||
{tabs[activeTab]?.items.map((templateItem) => {
|
||||
{/* 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} {...templateItem.item} />
|
||||
) : (
|
||||
|
||||
@ -8,7 +8,7 @@ export interface Attestation {
|
||||
shape: string
|
||||
date_created: string
|
||||
user_created: { id: string; first_name: string }
|
||||
to: Array<{ directus_users_id: string }>
|
||||
to: { directus_users_id: string }[]
|
||||
}
|
||||
|
||||
export const AttestationsContext = createContext<Attestation[]>([])
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user