Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export default [
rules: {
...reactHooks.configs.recommended.rules,
...jsxA11y.configs.recommended.rules,
'react-hooks/set-state-in-effect': 'warn',
},
},
]
6 changes: 5 additions & 1 deletion src/components/AvatarCropModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,14 @@ export function AvatarCropModal({
</div>

<div className="mt-4">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
<label
htmlFor="zoom"
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"
>
Zoom
</label>
<input
id="zoom"
type="range"
min={1}
max={3}
Expand Down
5 changes: 3 additions & 2 deletions src/components/DocsLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react'
import { TextAlignStart, ChevronLeft, ChevronRight, Menu } from 'lucide-react'
import { ChevronLeft, ChevronRight, Menu } from 'lucide-react'
import { GithubIcon } from '~/components/icons/GithubIcon'
import { DiscordIcon } from '~/components/icons/DiscordIcon'
import { Link, useMatches, useParams } from '@tanstack/react-router'
Expand All @@ -12,7 +12,7 @@
import { DocsCalloutQueryGG } from '~/components/DocsCalloutQueryGG'
import { twMerge } from 'tailwind-merge'
import { partners, PartnerImage } from '~/utils/partners'
import { GamFooter, GamHeader, GamVrec1 } from './Gam'
import { GamHeader, GamVrec1 } from './Gam'
import { AdGate } from '~/contexts/AdsContext'
import { SearchButton } from './SearchButton'
import { FrameworkSelect, useCurrentFramework } from './FrameworkSelect'
Expand Down Expand Up @@ -73,6 +73,7 @@
}, [isHovered])

return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events
<div
className="flex-1 flex items-center gap-2 min-w-0"
onClick={(e) => e.preventDefault()}
Expand Down Expand Up @@ -254,7 +255,7 @@
}

// Helper to get text color class from framework badge
const getFrameworkTextColor = (frameworkValue: string | undefined) => {

Check warning on line 258 in src/components/DocsLayout.tsx

View workflow job for this annotation

GitHub Actions / PR

'getFrameworkTextColor' is assigned a value but never used. Allowed unused vars must match /(^_)|(^__+$)|(^e$)|(^error$)/u
if (!frameworkValue) return 'text-gray-500'
const framework = frameworkOptions.find((f) => f.value === frameworkValue)

Expand Down
1 change: 1 addition & 0 deletions src/components/FeedEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ interface FeedEntryProps {
}
}

// eslint-disable-next-line @typescript-eslint/no-redeclare
export function FeedEntry({
entry,
showFullContent = false,
Expand Down
12 changes: 10 additions & 2 deletions src/components/FeedbackModerationTopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,14 @@ export function FeedbackModerationTopBar({
<FilterDropdownSection title="Date Range">
<div className="space-y-3">
<div>
<label className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">
<label
htmlFor="from"
className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1"
>
From
</label>
<input
id="from"
type="date"
value={filters.dateFrom || ''}
onChange={(e) =>
Expand All @@ -176,10 +180,14 @@ export function FeedbackModerationTopBar({
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">
<label
htmlFor="to"
className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1"
>
To
</label>
<input
id="to"
type="date"
value={filters.dateTo || ''}
onChange={(e) =>
Expand Down
1 change: 1 addition & 0 deletions src/components/ImageUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export function ImageUpload({
</button>
</div>
) : (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div
onClick={() => fileInputRef.current?.click()}
onDrop={handleDrop}
Expand Down
184 changes: 105 additions & 79 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,95 @@
} from '~/components/Collapsible'
import { Card } from '~/components/Card'

type LogoProps = {
showMenu: boolean
setShowMenu: React.Dispatch<React.SetStateAction<boolean>>
menuButtonRef: React.RefObject<HTMLButtonElement | null>
title?: React.ComponentType<any> | null
}

const LogoSection = ({
showMenu,
setShowMenu,
menuButtonRef,
title,
}: LogoProps) => {
const pointerInsideButtonRef = React.useRef(false)
const toggleMenu = () => {
setShowMenu((prev) => !prev)
}
return (
<>
<button
aria-label="Open Menu"
className={twMerge(
'flex items-center justify-center',
'transition-all duration-300 h-8 px-2 py-1 lg:px-0',
// At lg: only visible when Title exists (flyout mode)
// Below lg: always visible
title
? 'lg:w-9 lg:opacity-100 lg:translate-x-0'
: 'lg:w-0 lg:opacity-0 lg:-translate-x-full',
)}
ref={menuButtonRef}
onClick={toggleMenu}
onPointerEnter={(e) => {
// Enable hover to open flyout at md+ (but not touch)
if (window.innerWidth < 768 || e.pointerType === 'touch') return
if (pointerInsideButtonRef.current) return
pointerInsideButtonRef.current = true
setShowMenu(true)
}}
onPointerLeave={() => {
pointerInsideButtonRef.current = false
}}
>
{showMenu ? <X /> : <Menu />}
</button>
<Link
to="/"
className={twMerge(`inline-flex items-center gap-1.5 cursor-pointer`)}
>
<div className="w-[30px] inline-grid items-center grid-cols-1 grid-rows-1 [&>*]:transition-opacity [&>*]:duration-1000">
<img
src={'/images/logos/logo-color-100.png'}
alt=""
className="row-start-1 col-start-1 w-full group-hover:opacity-0"
/>
<img
src={'/images/logos/logo-black.svg'}
alt=""
className="row-start-1 col-start-1 w-full dark:opacity-0 opacity-0 group-hover:opacity-100"
/>
<img
src={'/images/logos/logo-white.svg'}
alt=""
className="row-start-1 col-start-1 w-full light:opacity-0 dark:block opacity-0 group-hover:opacity-100"
/>
</div>
<div>TanStack</div>
</Link>
</>
)
}

const MobileCard = ({
children,
isActive,
}: {
children: React.ReactNode
isActive?: boolean
}) => (
<Card
className={twMerge(
'md:contents border-gray-200/50 dark:border-gray-700/50 shadow-sm',
isActive && 'ring-2 ring-gray-400/30 dark:ring-gray-500/30',
)}
>
{children}
</Card>
)

export function Navbar({ children }: { children: React.ReactNode }) {
const matches = useMatches()
const capabilities = useCapabilities()
Expand Down Expand Up @@ -131,8 +220,6 @@
}, [])

const [showMenu, setShowMenu] = React.useState(false)
const pointerInsideButtonRef = React.useRef(false)

const largeMenuRef = React.useRef<HTMLDivElement>(null)
const menuButtonRef = React.useRef<HTMLButtonElement>(null)

Expand All @@ -143,64 +230,6 @@
additionalRefs: [largeMenuRef, menuButtonRef],
})

const toggleMenu = () => {
setShowMenu((prev) => !prev)
}

const LogoSection = () => (
<>
<button
aria-label="Open Menu"
className={twMerge(
'flex items-center justify-center',
'transition-all duration-300 h-8 px-2 py-1 lg:px-0',
// At lg: only visible when Title exists (flyout mode)
// Below lg: always visible
Title
? 'lg:w-9 lg:opacity-100 lg:translate-x-0'
: 'lg:w-0 lg:opacity-0 lg:-translate-x-full',
)}
ref={menuButtonRef}
onClick={toggleMenu}
onPointerEnter={(e) => {
// Enable hover to open flyout at md+ (but not touch)
if (window.innerWidth < 768 || e.pointerType === 'touch') return
if (pointerInsideButtonRef.current) return
pointerInsideButtonRef.current = true
setShowMenu(true)
}}
onPointerLeave={() => {
pointerInsideButtonRef.current = false
}}
>
{showMenu ? <X /> : <Menu />}
</button>
<Link
to="/"
className={twMerge(`inline-flex items-center gap-1.5 cursor-pointer`)}
>
<div className="w-[30px] inline-grid items-center grid-cols-1 grid-rows-1 [&>*]:transition-opacity [&>*]:duration-1000">
<img
src={'/images/logos/logo-color-100.png'}
alt=""
className="row-start-1 col-start-1 w-full group-hover:opacity-0"
/>
<img
src={'/images/logos/logo-black.svg'}
alt=""
className="row-start-1 col-start-1 w-full dark:opacity-0 opacity-0 group-hover:opacity-100"
/>
<img
src={'/images/logos/logo-white.svg'}
alt=""
className="row-start-1 col-start-1 w-full light:opacity-0 dark:block opacity-0 group-hover:opacity-100"
/>
</div>
<div>TanStack</div>
</Link>
</>
)

const loginButton = (
<>
{(() => {
Expand Down Expand Up @@ -278,11 +307,25 @@
>
<div className="flex items-center min-w-0">
<div className="flex items-center gap-2 font-black text-xl uppercase min-w-0">
<React.Suspense fallback={<LogoSection />}>
<React.Suspense
fallback={
<LogoSection
menuButtonRef={menuButtonRef}
setShowMenu={setShowMenu}
showMenu={showMenu}
title={Title}
/>
}
>
<LazyBrandContextMenu
className={twMerge(`flex items-center group flex-shrink-0`)}
>
<LogoSection />
<LogoSection
menuButtonRef={menuButtonRef}
setShowMenu={setShowMenu}
showMenu={showMenu}
title={Title}
/>
</LazyBrandContextMenu>
</React.Suspense>
{Title ? (
Expand Down Expand Up @@ -318,23 +361,6 @@

const linkClasses = `flex items-center justify-between gap-2 group px-3 py-3 md:px-2 md:py-1 rounded-lg hover:bg-gray-500/10 font-bold text-base md:text-sm`

const MobileCard = ({
children,
isActive,
}: {
children: React.ReactNode
isActive?: boolean
}) => (
<Card
className={twMerge(
'md:contents border-gray-200/50 dark:border-gray-700/50 shadow-sm',
isActive && 'ring-2 ring-gray-400/30 dark:ring-gray-500/30',
)}
>
{children}
</Card>
)

const items = (
<div className="contents md:block">
<div className="contents md:block">
Expand Down Expand Up @@ -363,7 +389,7 @@
return indexA - indexB
})
})().map((library, i) => {
const [prefix, name] = library.name.split(' ')

Check warning on line 392 in src/components/Navbar.tsx

View workflow job for this annotation

GitHub Actions / PR

'prefix' is assigned a value but never used. Allowed unused vars must match /(^_)|(^__+$)|(^e$)|(^error$)/u
const isActive = library.to === activeLibrary?.to

return (
Expand Down
12 changes: 10 additions & 2 deletions src/components/NotesModerationTopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,14 @@ export function NotesModerationTopBar({
<FilterDropdownSection title="Date Range">
<div className="space-y-3">
<div>
<label className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">
<label
htmlFor="from"
className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1"
>
From
</label>
<input
id="from"
type="date"
value={filters.dateFrom || ''}
onChange={(e) =>
Expand All @@ -135,10 +139,14 @@ export function NotesModerationTopBar({
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">
<label
htmlFor="to"
className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1"
>
To
</label>
<input
id="to"
type="date"
value={filters.dateTo || ''}
onChange={(e) =>
Expand Down
7 changes: 4 additions & 3 deletions src/components/SearchModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,15 @@
function SearchFiltersProvider({ children }: { children: React.ReactNode }) {
const userQuery = useCurrentUserQuery()
const [selectedLibrary, setSelectedLibrary] = React.useState('')
const lastUsedFramework = userQuery.data?.lastUsedFramework

// Get initial framework from user preference (DB if logged in, localStorage otherwise)
const getInitialFramework = React.useCallback(() => {
if (userQuery.data?.lastUsedFramework) {
return userQuery.data.lastUsedFramework
if (lastUsedFramework) {
return lastUsedFramework
}
return getStoredFrameworkPreference() || ''
}, [userQuery.data?.lastUsedFramework])
}, [lastUsedFramework])

const [selectedFramework, setSelectedFramework] =
React.useState(getInitialFramework)
Expand Down Expand Up @@ -752,7 +753,7 @@
)
}

const submitIconComponent = () => {

Check warning on line 756 in src/components/SearchModal.tsx

View workflow job for this annotation

GitHub Actions / PR

'submitIconComponent' is assigned a value but never used. Allowed unused vars must match /(^_)|(^__+$)|(^e$)|(^error$)/u
return <Search />
}

Expand Down Expand Up @@ -907,7 +908,7 @@
selectedFramework,
setSelectedLibrary,
setSelectedFramework,
libraryItems,

Check warning on line 911 in src/components/SearchModal.tsx

View workflow job for this annotation

GitHub Actions / PR

'libraryItems' is assigned a value but never used. Allowed unused vars must match /(^_)|(^__+$)|(^e$)|(^error$)/u
frameworkItems,
} = useSearchFilters()

Expand Down
6 changes: 5 additions & 1 deletion src/components/ShowcaseModerationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -522,10 +522,14 @@ export function ShowcaseModerationList({
{/* Moderation Note Input (for pending only) */}
{isPending && (
<div>
<label className="block text-sm font-semibold mb-2">
<label
htmlFor="note"
className="block text-sm font-semibold mb-2"
>
Internal Moderation Note (optional):
</label>
<textarea
id="note"
value={moderationNotes[showcase.id] || ''}
onChange={(e) =>
handleModerationNoteChange(
Expand Down
Loading
Loading