mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 01:35:39 -04:00
chore: upgrade Expo SDK 55 → 57 and React Native 0.83 → 0.86
This commit is contained in:
+18
-18
@@ -14,9 +14,9 @@
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "1.3.0",
|
||||
"@base-ui/react": "1.6.0",
|
||||
"@fontsource-variable/dm-sans": "5.2.8",
|
||||
"@fontsource-variable/geist-mono": "5.2.7",
|
||||
"@fontsource-variable/geist-mono": "5.2.8",
|
||||
"@fontsource/dm-serif-display": "5.2.8",
|
||||
"@lingui/core": "catalog:",
|
||||
"@lingui/react": "catalog:",
|
||||
@@ -24,24 +24,24 @@
|
||||
"@orpc/contract": "catalog:",
|
||||
"@orpc/tanstack-query": "catalog:",
|
||||
"@player.style/sutro": "0.2.1",
|
||||
"@rolldown/plugin-babel": "0.2.2",
|
||||
"@rolldown/plugin-babel": "0.2.3",
|
||||
"@sofa/api": "workspace:*",
|
||||
"@sofa/i18n": "workspace:*",
|
||||
"@tabler/icons-react": "3.40.0",
|
||||
"@tabler/icons-react": "3.44.0",
|
||||
"@tanstack/react-form": "catalog:",
|
||||
"@tanstack/react-hotkeys": "0.8.1",
|
||||
"@tanstack/react-hotkeys": "0.10.0",
|
||||
"@tanstack/react-query": "catalog:",
|
||||
"@tanstack/react-router": "1.168.5",
|
||||
"@tanstack/zod-adapter": "1.166.9",
|
||||
"@tanstack/react-router": "1.170.17",
|
||||
"@tanstack/zod-adapter": "1.167.0",
|
||||
"better-auth": "catalog:",
|
||||
"class-variance-authority": "0.7.1",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.1.1",
|
||||
"jotai": "2.19.0",
|
||||
"motion": "12.38.0",
|
||||
"jotai": "2.20.1",
|
||||
"motion": "12.42.2",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"shadcn": "4.1.0",
|
||||
"shadcn": "4.13.0",
|
||||
"sonner": "2.0.7",
|
||||
"tailwind-merge": "catalog:",
|
||||
"thumbhash": "catalog:",
|
||||
@@ -52,23 +52,23 @@
|
||||
"@lingui/babel-plugin-lingui-macro": "catalog:",
|
||||
"@lingui/vite-plugin": "catalog:",
|
||||
"@sofa/tsconfig": "workspace:*",
|
||||
"@tailwindcss/vite": "4.2.2",
|
||||
"@tanstack/devtools-vite": "0.6.0",
|
||||
"@tanstack/react-devtools": "0.10.0",
|
||||
"@tanstack/react-query-devtools": "5.95.2",
|
||||
"@tanstack/react-router-devtools": "1.166.11",
|
||||
"@tanstack/router-plugin": "1.167.6",
|
||||
"@tailwindcss/vite": "4.3.2",
|
||||
"@tanstack/devtools-vite": "0.8.1",
|
||||
"@tanstack/react-devtools": "0.10.8",
|
||||
"@tanstack/react-query-devtools": "5.101.2",
|
||||
"@tanstack/react-router-devtools": "1.167.0",
|
||||
"@tanstack/router-plugin": "1.168.19",
|
||||
"@types/bun": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@vitejs/plugin-react": "6.0.1",
|
||||
"@vitejs/plugin-react": "6.0.3",
|
||||
"@vitest/browser": "catalog:",
|
||||
"@vitest/browser-playwright": "catalog:",
|
||||
"babel-plugin-react-compiler": "1.0.0",
|
||||
"tailwindcss": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vite": "8.0.3",
|
||||
"vite": "8.1.3",
|
||||
"vitest": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ interface TitleRowItem {
|
||||
}
|
||||
|
||||
type TitleStatus = "in_watchlist" | "watching" | "caught_up" | "completed";
|
||||
const EMPTY_STATUSES: Record<string, TitleStatus> = {};
|
||||
const EMPTY_PROGRESS: Record<string, { watched: number; total: number }> = {};
|
||||
|
||||
interface FilterableTitleRowProps {
|
||||
heading: string;
|
||||
@@ -42,8 +44,8 @@ export function FilterableTitleRow({
|
||||
mediaType,
|
||||
defaultItems,
|
||||
genres,
|
||||
userStatuses: initialStatuses = {},
|
||||
episodeProgress: initialProgress = {},
|
||||
userStatuses: initialStatuses = EMPTY_STATUSES,
|
||||
episodeProgress: initialProgress = EMPTY_PROGRESS,
|
||||
}: FilterableTitleRowProps) {
|
||||
const [selectedGenre, setSelectedGenre] = useState<number | null>(null);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
import { IconAlertTriangle } from "@tabler/icons-react";
|
||||
import type { ErrorComponentProps } from "@tanstack/react-router";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
export function RouteError({ reset }: ErrorComponentProps) {
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-6 py-24 text-center">
|
||||
<div className="border-destructive/20 bg-destructive/5 text-destructive flex size-14 items-center justify-center rounded-full border">
|
||||
@@ -23,6 +25,7 @@ export function RouteError({ reset }: ErrorComponentProps) {
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t`Try again`}
|
||||
onClick={reset}
|
||||
className="group bg-primary text-primary-foreground hover:shadow-primary/20 relative inline-flex h-10 items-center justify-center gap-2 overflow-hidden rounded-lg px-5 text-sm font-medium transition-shadow hover:shadow-lg"
|
||||
>
|
||||
|
||||
@@ -171,6 +171,7 @@ export function AccountSection({
|
||||
render={
|
||||
<button
|
||||
type="button"
|
||||
aria-label={avatarUrl ? t`Remove profile picture` : t`Upload profile picture`}
|
||||
onClick={avatarUrl ? handleRemoveAvatar : () => fileInputRef.current?.click()}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans, useLingui } from "@lingui/react/macro";
|
||||
import { IconCloudUpload, IconLink } from "@tabler/icons-react";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -209,9 +209,6 @@ function ImportSourceCard({ config }: { config: SourceConfig }) {
|
||||
onError: (err) => {
|
||||
toast.error(getErrorMessage(err, t, t`Failed to parse file`));
|
||||
},
|
||||
onSettled: () => {
|
||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -236,7 +233,14 @@ function ImportSourceCard({ config }: { config: SourceConfig }) {
|
||||
} | null>(null);
|
||||
|
||||
function handleFileSelect(file: File) {
|
||||
parseMutation.mutate({ source: config.source, file });
|
||||
parseMutation.mutate(
|
||||
{ source: config.source, file },
|
||||
{
|
||||
onSettled: () => {
|
||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function handleImport() {
|
||||
@@ -323,13 +327,12 @@ function ImportSourceCard({ config }: { config: SourceConfig }) {
|
||||
}
|
||||
|
||||
// Clean up poll timer on unmount or dialog close
|
||||
const stopPollingRef = useRef(() => {
|
||||
const stopPolling = useCallback(() => {
|
||||
if (pollTimerRef.current) {
|
||||
clearInterval(pollTimerRef.current);
|
||||
pollTimerRef.current = null;
|
||||
}
|
||||
});
|
||||
const stopPolling = stopPollingRef.current;
|
||||
}, []);
|
||||
|
||||
function handleClose() {
|
||||
stopPolling();
|
||||
|
||||
@@ -4,7 +4,6 @@ export function SofaLogo({ className }: { className?: string }) {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
className={className ?? "size-6"}
|
||||
role="img"
|
||||
aria-label="Sofa"
|
||||
>
|
||||
<path
|
||||
|
||||
@@ -125,7 +125,7 @@ function QuickAddButton({ id, userStatus }: { id: string; userStatus?: TitleStat
|
||||
<TooltipTrigger
|
||||
onClick={handleClick}
|
||||
className="absolute end-2 top-2 z-10 flex size-8 items-center justify-center rounded-full bg-black/50 text-white opacity-60 backdrop-blur-sm transition-opacity hover:bg-black/70 focus-visible:opacity-100 sm:opacity-0 sm:group-hover:opacity-100"
|
||||
render={<button type="button" />}
|
||||
render={<button type="button" aria-label={t`Add to Watchlist`} />}
|
||||
>
|
||||
{!addToWatchlistMutation.isPending && <IconPlus className="size-4" />}
|
||||
{addToWatchlistMutation.isPending && <IconLoader className="size-4 animate-spin" />}
|
||||
|
||||
@@ -22,7 +22,6 @@ export function StarRating({ value, onChange }: StarRatingProps) {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center gap-0.5"
|
||||
role="radiogroup"
|
||||
aria-label={t`Rating`}
|
||||
onMouseLeave={() => setHover(0)}
|
||||
>
|
||||
@@ -32,8 +31,7 @@ export function StarRating({ value, onChange }: StarRatingProps) {
|
||||
<motion.button
|
||||
key={star}
|
||||
type="button"
|
||||
role="radio"
|
||||
aria-checked={star === value}
|
||||
aria-pressed={star === value}
|
||||
aria-label={t`Rate ${plural(star, { one: "# star", other: "# stars" })}`}
|
||||
onClick={() => onChange(star === value ? 0 : star)}
|
||||
onMouseEnter={() => setHover(star)}
|
||||
|
||||
@@ -4,7 +4,6 @@ export function TmdbLogo({ className }: { className?: string }) {
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 190.24 81.52"
|
||||
className={className}
|
||||
role="img"
|
||||
aria-label="TMDB"
|
||||
>
|
||||
<defs>
|
||||
|
||||
@@ -29,7 +29,6 @@ function ButtonGroup({
|
||||
}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
|
||||
return (
|
||||
<div
|
||||
role="group"
|
||||
data-slot="button-group"
|
||||
data-orientation={orientation}
|
||||
className={cn(buttonGroupVariants({ orientation }), className)}
|
||||
|
||||
@@ -71,7 +71,6 @@ function Field({
|
||||
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
|
||||
return (
|
||||
<div
|
||||
role="group"
|
||||
data-slot="field"
|
||||
data-orientation={orientation}
|
||||
className={cn(fieldVariants({ orientation }), className)}
|
||||
|
||||
@@ -10,7 +10,6 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="input-group"
|
||||
role="group"
|
||||
className={cn(
|
||||
"group/input-group border-input bg-input/20 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot=input-group-control]:focus-visible]:ring-ring/30 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:bg-input/30 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 relative flex h-7 w-full min-w-0 items-center rounded-md border transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-data-[align=block-end]:rounded-md has-data-[align=block-start]:rounded-md has-[[data-slot=input-group-control]:focus-visible]:ring-2 has-[[data-slot][aria-invalid=true]]:ring-2 has-[textarea]:rounded-md has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pe-1.5 has-[>[data-align=inline-start]]:[&>input]:ps-1.5",
|
||||
className,
|
||||
@@ -44,7 +43,7 @@ function InputGroupAddon({
|
||||
align = "inline-start",
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
|
||||
const focusInput = (e: React.SyntheticEvent<HTMLDivElement>) => {
|
||||
const focusInput = (e: React.PointerEvent<HTMLDivElement>) => {
|
||||
if ((e.target as HTMLElement).closest("button")) {
|
||||
return;
|
||||
}
|
||||
@@ -52,16 +51,10 @@ function InputGroupAddon({
|
||||
};
|
||||
return (
|
||||
<div
|
||||
role="group"
|
||||
data-slot="input-group-addon"
|
||||
data-align={align}
|
||||
className={cn(inputGroupAddonVariants({ align }), className)}
|
||||
onClick={focusInput}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
focusInput(e);
|
||||
}
|
||||
}}
|
||||
onPointerDown={focusInput}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,6 @@ import { cn } from "@/lib/utils";
|
||||
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
role="list"
|
||||
data-slot="item-group"
|
||||
className={cn(
|
||||
"group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2",
|
||||
|
||||
@@ -11,7 +11,7 @@ function Slider({
|
||||
max = 100,
|
||||
...props
|
||||
}: SliderPrimitive.Root.Props) {
|
||||
const _values = React.useMemo(
|
||||
const values = React.useMemo(
|
||||
() => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]),
|
||||
[value, defaultValue, min, max],
|
||||
);
|
||||
@@ -37,7 +37,7 @@ function Slider({
|
||||
className="bg-primary select-none data-horizontal:h-full data-vertical:w-full"
|
||||
/>
|
||||
</SliderPrimitive.Track>
|
||||
{Array.from({ length: _values.length }, (_, index) => (
|
||||
{Array.from({ length: values.length }, (_, index) => (
|
||||
<SliderPrimitive.Thumb
|
||||
data-slot="slider-thumb"
|
||||
key={index}
|
||||
|
||||
@@ -7,7 +7,6 @@ function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
||||
const { t } = useLingui();
|
||||
return (
|
||||
<IconLoader
|
||||
role="status"
|
||||
aria-label={t`Loading`}
|
||||
className={cn("size-4 animate-spin", className)}
|
||||
{...props}
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as React from "react";
|
||||
const MOBILE_BREAKPOINT = 768;
|
||||
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined);
|
||||
const [isMobile, setIsMobile] = React.useState(() => window.innerWidth < MOBILE_BREAKPOINT);
|
||||
|
||||
React.useEffect(() => {
|
||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
||||
@@ -11,9 +11,8 @@ export function useIsMobile() {
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||
};
|
||||
mql.addEventListener("change", onChange);
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||
return () => mql.removeEventListener("change", onChange);
|
||||
}, []);
|
||||
|
||||
return !!isMobile;
|
||||
return isMobile;
|
||||
}
|
||||
|
||||
@@ -91,6 +91,8 @@ function AppShell() {
|
||||
}
|
||||
|
||||
function GlobalNotFound() {
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<div className="relative flex min-h-screen flex-col items-center justify-center overflow-hidden px-6">
|
||||
<div className="bg-primary/4 pointer-events-none absolute top-[40%] left-1/2 h-[500px] w-[500px] -translate-x-1/2 -translate-y-1/2 rounded-full blur-[160px]" />
|
||||
@@ -121,6 +123,7 @@ function GlobalNotFound() {
|
||||
>
|
||||
<a
|
||||
href="/"
|
||||
aria-label={t`Return home`}
|
||||
className="group bg-primary text-primary-foreground hover:shadow-primary/20 relative inline-flex h-10 items-center justify-center overflow-hidden rounded-lg px-6 text-sm font-medium transition-shadow hover:shadow-lg"
|
||||
>
|
||||
<span className="relative z-10">
|
||||
@@ -141,6 +144,8 @@ function GlobalNotFound() {
|
||||
}
|
||||
|
||||
function GlobalError({ reset }: { error: Error; reset: () => void }) {
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-6 py-24 text-center">
|
||||
<div className="border-destructive/20 bg-destructive/5 text-destructive flex size-14 items-center justify-center rounded-full border">
|
||||
@@ -160,6 +165,7 @@ function GlobalError({ reset }: { error: Error; reset: () => void }) {
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t`Try again`}
|
||||
onClick={reset}
|
||||
className="group bg-primary text-primary-foreground hover:shadow-primary/20 relative inline-flex h-10 items-center justify-center gap-2 overflow-hidden rounded-lg px-5 text-sm font-medium transition-shadow hover:shadow-lg"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user