mirror of
https://github.com/lukevella/rallly.git
synced 2025-08-06 01:48:32 +02:00
⚡️ Faster tab switches (#1701)
This commit is contained in:
parent
486bd50139
commit
590e19ae16
6 changed files with 59 additions and 29 deletions
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
|
@ -1,10 +1,14 @@
|
||||||
{
|
{
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll": "explicit"
|
"source.fixAll.biome": "explicit",
|
||||||
|
"source.organizeImports.biome": "explicit"
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
},
|
},
|
||||||
|
"[typescriptreact]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"typescript.preferences.importModuleSpecifier": "shortest",
|
"typescript.preferences.importModuleSpecifier": "shortest",
|
||||||
"cSpell.words": ["Rallly", "Vella"],
|
"cSpell.words": ["Rallly", "Vella"],
|
||||||
|
@ -14,5 +18,6 @@
|
||||||
"typescript.preferences.preferTypeOnlyAutoImports": true,
|
"typescript.preferences.preferTypeOnlyAutoImports": true,
|
||||||
"typescript.tsserver.log": "verbose",
|
"typescript.tsserver.log": "verbose",
|
||||||
"typescript.tsserver.trace": "messages",
|
"typescript.tsserver.trace": "messages",
|
||||||
"references.preferredLocation": "view"
|
"references.preferredLocation": "view",
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
import { cn } from "@rallly/ui";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@rallly/ui/page-tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@rallly/ui/page-tabs";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
@ -9,6 +10,8 @@ export function EventsTabbedView({ children }: { children: React.ReactNode }) {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const name = "status";
|
const name = "status";
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [isPending, startTransition] = React.useTransition();
|
||||||
|
const [tab, setTab] = React.useState(searchParams.get(name) ?? "upcoming");
|
||||||
const handleTabChange = React.useCallback(
|
const handleTabChange = React.useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
const params = new URLSearchParams(searchParams);
|
const params = new URLSearchParams(searchParams);
|
||||||
|
@ -17,15 +20,16 @@ export function EventsTabbedView({ children }: { children: React.ReactNode }) {
|
||||||
params.delete("page");
|
params.delete("page");
|
||||||
|
|
||||||
const newUrl = `?${params.toString()}`;
|
const newUrl = `?${params.toString()}`;
|
||||||
router.replace(newUrl, { scroll: false });
|
startTransition(() => {
|
||||||
|
setTab(value);
|
||||||
|
router.replace(newUrl, { scroll: false });
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[router, searchParams],
|
[router, searchParams],
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = searchParams.get(name) ?? "upcoming";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs value={value} onValueChange={handleTabChange}>
|
<Tabs value={tab} onValueChange={handleTabChange}>
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="upcoming">
|
<TabsTrigger value="upcoming">
|
||||||
<Trans i18nKey="upcoming" defaults="Upcoming" />
|
<Trans i18nKey="upcoming" defaults="Upcoming" />
|
||||||
|
@ -34,7 +38,15 @@ export function EventsTabbedView({ children }: { children: React.ReactNode }) {
|
||||||
<Trans i18nKey="past" defaults="Past" />
|
<Trans i18nKey="past" defaults="Past" />
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent tabIndex={-1} value={value} key={value}>
|
<TabsContent
|
||||||
|
tabIndex={-1}
|
||||||
|
value={tab}
|
||||||
|
key={tab}
|
||||||
|
className={cn(
|
||||||
|
"transition-opacity",
|
||||||
|
isPending ? "opacity-50 delay-200 pointer-events-none" : "",
|
||||||
|
)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
|
@ -138,13 +138,13 @@ export default async function Page({
|
||||||
</PageDescription>
|
</PageDescription>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<PageContent>
|
<PageContent>
|
||||||
<EventsTabbedView>
|
<div className="space-y-4">
|
||||||
<div className="space-y-4">
|
<SearchInput
|
||||||
<SearchInput
|
placeholder={t("searchEventsPlaceholder", {
|
||||||
placeholder={t("searchEventsPlaceholder", {
|
defaultValue: "Search events by title...",
|
||||||
defaultValue: "Search events by title...",
|
})}
|
||||||
})}
|
/>
|
||||||
/>
|
<EventsTabbedView>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{paginatedEvents.length === 0 && (
|
{paginatedEvents.length === 0 && (
|
||||||
<ScheduledEventEmptyState status={status} />
|
<ScheduledEventEmptyState status={status} />
|
||||||
|
@ -177,8 +177,8 @@ export default async function Page({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</EventsTabbedView>
|
||||||
</EventsTabbedView>
|
</div>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
|
|
@ -168,13 +168,13 @@ export default async function Page({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PageContent className="space-y-4">
|
<PageContent className="space-y-4">
|
||||||
|
<SearchInput
|
||||||
|
placeholder={t("searchPollsPlaceholder", {
|
||||||
|
defaultValue: "Search polls by title...",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
<PollsTabbedView>
|
<PollsTabbedView>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<SearchInput
|
|
||||||
placeholder={t("searchPollsPlaceholder", {
|
|
||||||
defaultValue: "Search polls by title...",
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
{polls.length === 0 ? (
|
{polls.length === 0 ? (
|
||||||
<PollsEmptyState />
|
<PollsEmptyState />
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@rallly/ui/page-tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@rallly/ui/page-tabs";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import { Trans } from "@/components/trans";
|
import { Trans } from "@/components/trans";
|
||||||
|
|
||||||
|
import { cn } from "@rallly/ui";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export function PollsTabbedView({ children }: { children: React.ReactNode }) {
|
export function PollsTabbedView({ children }: { children: React.ReactNode }) {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const name = "status";
|
const name = "status";
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [isPending, startTransition] = React.useTransition();
|
||||||
|
const [tab, setTab] = React.useState(searchParams.get(name) ?? "live");
|
||||||
const handleTabChange = React.useCallback(
|
const handleTabChange = React.useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
const params = new URLSearchParams(searchParams);
|
const params = new URLSearchParams(searchParams);
|
||||||
|
@ -16,16 +20,17 @@ export function PollsTabbedView({ children }: { children: React.ReactNode }) {
|
||||||
|
|
||||||
params.delete("page");
|
params.delete("page");
|
||||||
|
|
||||||
const newUrl = `?${params.toString()}`;
|
startTransition(() => {
|
||||||
router.replace(newUrl, { scroll: false });
|
setTab(value);
|
||||||
|
const newUrl = `?${params.toString()}`;
|
||||||
|
router.replace(newUrl, { scroll: false });
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[router, searchParams],
|
[router, searchParams],
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = searchParams.get(name) ?? "live";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs value={value} onValueChange={handleTabChange}>
|
<Tabs value={tab} onValueChange={handleTabChange}>
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="live">
|
<TabsTrigger value="live">
|
||||||
<Trans i18nKey="pollStatusOpen" defaults="Live" />
|
<Trans i18nKey="pollStatusOpen" defaults="Live" />
|
||||||
|
@ -37,7 +42,15 @@ export function PollsTabbedView({ children }: { children: React.ReactNode }) {
|
||||||
<Trans i18nKey="pollStatusFinalized" defaults="Finalized" />
|
<Trans i18nKey="pollStatusFinalized" defaults="Finalized" />
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent tabIndex={-1} value={value} key={value}>
|
<TabsContent
|
||||||
|
tabIndex={-1}
|
||||||
|
value={tab}
|
||||||
|
key={tab}
|
||||||
|
className={cn(
|
||||||
|
"transition-opacity",
|
||||||
|
isPending ? "opacity-50 delay-200 pointer-events-none" : "",
|
||||||
|
)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
|
@ -18,7 +18,7 @@ const Progress = React.forwardRef<
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ProgressPrimitive.Indicator
|
<ProgressPrimitive.Indicator
|
||||||
className="bg-primary h-full w-full flex-1 transition-all"
|
className="bg-gradient-to-r from-purple-500 to-indigo-500 h-full w-full flex-1 transition-all"
|
||||||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||||
/>
|
/>
|
||||||
</ProgressPrimitive.Root>
|
</ProgressPrimitive.Root>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue