Reviewed-on: #433 Co-authored-by: miteruzo <miteruzo@naver.com>
このコミットはプルリクエスト #433 でマージされました。
このコミットが含まれているのは:
@@ -1,19 +1,20 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { useLocale, useLocaleNavigate } from '@/lib/useLocale'
|
||||
import SidebarComponent from '@/components/layout/SidebarComponent'
|
||||
import TagLink from '@/components/TagLink'
|
||||
import { fetchMaterialTagTree, parseMaterialFilter } from '@/lib/materials'
|
||||
import { materialsKeys } from '@/lib/queryKeys'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
|
||||
import type { CSSProperties, Dispatch, FC, ReactNode, SetStateAction } from 'react'
|
||||
|
||||
import type { MaterialFilter, MaterialSidebarTag, Tag } from '@/types'
|
||||
|
||||
const FILTERS: MaterialFilter[] = ['missing', 'present', 'any']
|
||||
const FILTER_LABELS: Record<MaterialFilter, string> = { present: '有', missing: '無', any: '全' }
|
||||
|
||||
|
||||
const px = (value: string): number => {
|
||||
@@ -83,7 +84,7 @@ const tagSelectionShellClass = (selected: boolean): string =>
|
||||
const updateMaterialFilterQuery = (
|
||||
pathname: string,
|
||||
locationSearch: string,
|
||||
navigate: ReturnType<typeof useNavigate>,
|
||||
navigate: ReturnType<typeof useLocaleNavigate>,
|
||||
materialFilter: MaterialFilter,
|
||||
) => {
|
||||
const qs = new URLSearchParams (locationSearch)
|
||||
@@ -95,24 +96,27 @@ const updateMaterialFilterQuery = (
|
||||
const MaterialFilterButtons: FC<{ materialFilter: MaterialFilter
|
||||
onChange: (materialFilter: MaterialFilter) => void }> = (
|
||||
{ materialFilter, onChange },
|
||||
) => (
|
||||
<div className="flex flex-wrap gap-2 justify-end md:justify-start flex-center">
|
||||
<label className="my-auto text-sm font-bold">素材:</label>
|
||||
{FILTERS.map (value => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
onClick={() => onChange (value)}
|
||||
className={cn (
|
||||
'rounded-full border px-3 py-1 text-sm',
|
||||
(materialFilter === value
|
||||
? ['border-sky-500 bg-sky-50 text-sky-700 dark:border-sky-400',
|
||||
'dark:bg-sky-950 dark:text-sky-100']
|
||||
: ['border-neutral-300 bg-white text-neutral-700 dark:border-stone-700',
|
||||
'dark:bg-stone-900 dark:text-stone-200']))}>
|
||||
{FILTER_LABELS[value]}
|
||||
</button>))}
|
||||
</div>)
|
||||
) => {
|
||||
const { label } = useUITranslation ('materials')
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2 justify-end md:justify-start flex-center">
|
||||
<label className="my-auto text-sm font-bold">{label ('sidebar.label')}</label>
|
||||
{FILTERS.map (value => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
onClick={() => onChange (value)}
|
||||
className={cn (
|
||||
'rounded-full border px-3 py-1 text-sm',
|
||||
(materialFilter === value
|
||||
? ['border-sky-500 bg-sky-50 text-sky-700 dark:border-sky-400',
|
||||
'dark:bg-sky-950 dark:text-sky-100']
|
||||
: ['border-neutral-300 bg-white text-neutral-700 dark:border-stone-700',
|
||||
'dark:bg-stone-900 dark:text-stone-200']))}>
|
||||
{label (`sidebar.${value}`)}
|
||||
</button>))}
|
||||
</div>)
|
||||
}
|
||||
|
||||
|
||||
const MaterialTreeNode: FC<{
|
||||
@@ -125,10 +129,11 @@ const MaterialTreeNode: FC<{
|
||||
tag: MaterialSidebarTag
|
||||
}> = ({ materialFilter, nestLevel = 0, onChildren, openTags, selectedTagId,
|
||||
setOpenTags, tag }) => {
|
||||
const locale = useLocale ()
|
||||
const open = Boolean (openTags[tag.id])
|
||||
const { data } = useQuery ({
|
||||
queryKey: materialsKeys.tree ({ parentId: tag.id, materialFilter }),
|
||||
queryFn: () => fetchMaterialTagTree ({ parentId: tag.id, materialFilter }),
|
||||
queryKey: materialsKeys.tree ({ parentId: tag.id, materialFilter }, locale),
|
||||
queryFn: () => fetchMaterialTagTree ({ parentId: tag.id, materialFilter }, locale),
|
||||
enabled: open && tag.hasChildren && tag.children.length === 0})
|
||||
|
||||
useEffect (() => {
|
||||
@@ -200,6 +205,7 @@ const MobileMaterialTreeNode: FC<{ depth?: number
|
||||
tag,
|
||||
},
|
||||
) => {
|
||||
const locale = useLocale ()
|
||||
const open = Boolean (openTags[tag.id])
|
||||
const tagColumnRef = useRef<HTMLDivElement | null> (null)
|
||||
const chipRef = useRef<HTMLDivElement | null> (null)
|
||||
@@ -210,8 +216,8 @@ const MobileMaterialTreeNode: FC<{ depth?: number
|
||||
const [tagLinkInlineSizePx, setTagLinkInlineSizePx] = useState<number | null> (null)
|
||||
const [childAvailableInlineSizePx, setChildAvailableInlineSizePx] = useState<number | null> (null)
|
||||
const { data } = useQuery ({
|
||||
queryKey: materialsKeys.tree ({ parentId: tag.id, materialFilter }),
|
||||
queryFn: () => fetchMaterialTagTree ({ parentId: tag.id, materialFilter }),
|
||||
queryKey: materialsKeys.tree ({ parentId: tag.id, materialFilter }, locale),
|
||||
queryFn: () => fetchMaterialTagTree ({ parentId: tag.id, materialFilter }, locale),
|
||||
enabled: open && tag.hasChildren && tag.children.length === 0})
|
||||
|
||||
useEffect (() => {
|
||||
@@ -382,8 +388,10 @@ const MobileMaterialTreeNode: FC<{ depth?: number
|
||||
|
||||
|
||||
const MaterialSidebar: FC = () => {
|
||||
const { label } = useUITranslation ('materials')
|
||||
const locale = useLocale ()
|
||||
const location = useLocation ()
|
||||
const navigate = useNavigate ()
|
||||
const navigate = useLocaleNavigate ()
|
||||
const qs = new URLSearchParams (location.search)
|
||||
const materialFilter = parseMaterialFilter (qs.get ('material_filter'), 'any')
|
||||
const selectedTagId = Number (qs.get ('tag_id') ?? 0) || null
|
||||
@@ -395,8 +403,8 @@ const MaterialSidebar: FC = () => {
|
||||
useState<number | null> (null)
|
||||
|
||||
const { data: rootTags = [], isLoading, isError } = useQuery ({
|
||||
queryKey: materialsKeys.tree ({ parentId: null, materialFilter }),
|
||||
queryFn: () => fetchMaterialTagTree ({ parentId: null, materialFilter })})
|
||||
queryKey: materialsKeys.tree ({ parentId: null, materialFilter }, locale),
|
||||
queryFn: () => fetchMaterialTagTree ({ parentId: null, materialFilter }, locale)})
|
||||
|
||||
useEffect (() => {
|
||||
setDesktopTags (rootTags)
|
||||
@@ -494,10 +502,12 @@ const MaterialSidebar: FC = () => {
|
||||
materialFilter={materialFilter}
|
||||
onChange={handleFilterChange}/>
|
||||
{isLoading && (
|
||||
<p className="text-sm text-neutral-500 dark:text-stone-400">読込中……</p>)}
|
||||
<p className="text-sm text-neutral-500 dark:text-stone-400">
|
||||
{label ('shared:messages.loadingEllipsis')}
|
||||
</p>)}
|
||||
{isError && (
|
||||
<p className="text-sm text-red-600 dark:text-red-300">
|
||||
タグ一覧の取得に失敗しました.
|
||||
{label ('sidebar.error')}
|
||||
</p>)}
|
||||
{(!isLoading && !isError) && (
|
||||
<ul>
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする