diff --git a/src/app/(feed)/feed/SidebarCategories.tsx b/src/app/(feed)/feed/SidebarCategories.tsx index 8e0a875..5235927 100644 --- a/src/app/(feed)/feed/SidebarCategories.tsx +++ b/src/app/(feed)/feed/SidebarCategories.tsx @@ -67,6 +67,7 @@ export function SidebarCategories() { useCheckFilteredFeedItemsForCategory(); const setFeedFilter = useSetAtom(feedFilterAtom); + const setDateFilter = useSetAtom(dateFilterAtom); const [categoryFilter, setCategoryFilter] = useAtom(categoryFilterAtom); const { contentCategories } = useContentCategories(); @@ -92,7 +93,10 @@ export function SidebarCategories() { updateCategoryFilter(-1)} + onClick={() => { + updateCategoryFilter(-1); + setDateFilter(1); + }} > {!hasAnyItems && ( diff --git a/src/app/(feed)/feed/SidebarFeeds.tsx b/src/app/(feed)/feed/SidebarFeeds.tsx index b546ad5..c6fab83 100644 --- a/src/app/(feed)/feed/SidebarFeeds.tsx +++ b/src/app/(feed)/feed/SidebarFeeds.tsx @@ -65,6 +65,7 @@ function useCheckFilteredFeedItemsForFeed() { export function SidebarFeeds() { const { feeds } = useFeeds(); + const setDateFilter = useSetAtom(dateFilterAtom); const [feedFilter, setFeedFilter] = useAtom(feedFilterAtom); const checkFilteredFeedItemsForFeed = useCheckFilteredFeedItemsForFeed(); @@ -90,7 +91,10 @@ export function SidebarFeeds() { setFeedFilter(-1)} + onClick={() => { + setFeedFilter(-1); + setDateFilter(1); + }} > {!hasAnyItems && ( @@ -108,7 +112,10 @@ export function SidebarFeeds() { setFeedFilter(feed.id)} + onClick={() => { + setFeedFilter(feed.id); + setDateFilter(30); + }} > {!feed.hasEntries && ( diff --git a/src/app/(feed)/feed/UserManagementButton.tsx b/src/app/(feed)/feed/UserManagementButton.tsx index 131d072..39c2509 100644 --- a/src/app/(feed)/feed/UserManagementButton.tsx +++ b/src/app/(feed)/feed/UserManagementButton.tsx @@ -11,6 +11,11 @@ import { DropdownMenuLabel, DropdownMenuTrigger, } from "~/components/ui/dropdown-menu"; +import { + ResponsiveDropdown, + ResponsiveDropdownLabel, + ResponsiveDropdownMenuItem, +} from "~/components/ui/responsive-dropdown"; import { SidebarMenu, SidebarMenuButton, @@ -33,8 +38,9 @@ export function UserManagementNavItem() { return ( - - + - - - -
-

- {data?.user.name || "Serial User"} -

-

- {data?.user.email} -

-
-
- - - -
- + }, + }); + }} + > + {isSigningOut ? ( + + ) : ( + "Sign Out" + )} + + +
); diff --git a/src/app/(feed)/feed/edit/FeedCategoryCombobox.tsx b/src/app/(feed)/feed/edit/FeedCategoryCombobox.tsx index 46a3ed6..8d23d60 100644 --- a/src/app/(feed)/feed/edit/FeedCategoryCombobox.tsx +++ b/src/app/(feed)/feed/edit/FeedCategoryCombobox.tsx @@ -1,13 +1,12 @@ "use client"; +import { ListFilterPlusIcon } from "lucide-react"; import * as React from "react"; -import { Check, ChevronsUpDown, ListFilterPlusIcon } from "lucide-react"; -import { cn } from "~/lib/utils"; +import { useState } from "react"; import { Button } from "~/components/ui/button"; import { Command, - CommandEmpty, CommandGroup, CommandInput, CommandItem, @@ -17,7 +16,6 @@ import { PopoverContent, PopoverTrigger, } from "~/components/ui/popover"; -import { useState } from "react"; type FeedCategoryComboboxProps = { options: diff --git a/src/app/(feed)/feed/import/opml/OPMLSubscriptionImport.tsx b/src/app/(feed)/feed/import/opml/OPMLSubscriptionImport.tsx index fefd009..99119c1 100644 --- a/src/app/(feed)/feed/import/opml/OPMLSubscriptionImport.tsx +++ b/src/app/(feed)/feed/import/opml/OPMLSubscriptionImport.tsx @@ -6,6 +6,7 @@ import { type SubscriptionImportMethodProps } from "../types"; import { parseOPMLSubscriptionInput } from "./parseOPMLSubscriptionInput"; export function OPMLSubscriptionImport({ + importedChannels, setImportedChannels, }: SubscriptionImportMethodProps) { const [inputElement, setInputElement] = useState( @@ -44,7 +45,7 @@ export function OPMLSubscriptionImport({ multiple={false} onChange={onSelectFiles} > - {!!inputElement?.files?.length && ( + {!!importedChannels && inputElement && ( diff --git a/src/components/CustomVideoPlayer/index.tsx b/src/components/CustomVideoPlayer/index.tsx index d97c701..f364763 100644 --- a/src/components/CustomVideoPlayer/index.tsx +++ b/src/components/CustomVideoPlayer/index.tsx @@ -2,16 +2,11 @@ "use client"; import clsx from "clsx"; -import { - FullscreenIcon, - PlayIcon, - MaximizeIcon, - MinimizeIcon, - PlusIcon, - MinusIcon, -} from "lucide-react"; +import { MaximizeIcon, MinimizeIcon, PlayIcon } from "lucide-react"; import YouTube from "react-youtube"; +import { useFlagState } from "~/lib/hooks/useFlagState"; import { transformSecondsToFormattedTime } from "~/lib/transformSecondsToFormattedTime"; +import { ButtonWithShortcut } from "../ButtonWithShortcut"; import { useKeyboard } from "../KeyboardProvider"; import { Button } from "../ui/button"; import { Slider } from "../ui/slider"; @@ -22,8 +17,6 @@ import { } from "./CustomVideoPlayerProvider"; import { YOUTUBE_PLAYBACK_SPEEDS, YOUTUBE_PLAYER_STATES } from "./constants"; import { useVideoShortcuts } from "./useYouTubeVideoShortcuts"; -import { ButtonWithShortcut } from "../ButtonWithShortcut"; -import { useFlagState } from "~/lib/hooks/useFlagState"; interface IResponsiveVideoProps { videoID?: string; @@ -161,12 +154,13 @@ function CustomVideoPlayerContent(props: IResponsiveVideoProps) { {videoProgress >= videoDuration - 5 ? "Live" : "Go Live"} )} - {videoType === "video" && ( -
- {transformSecondsToFormattedTime(videoProgress)} /{" "} - {transformSecondsToFormattedTime(videoDuration)} -
- )} + {videoType === "video" || + (videoType === "live" && videoProgress < videoDuration && ( +
+ {transformSecondsToFormattedTime(videoProgress)} /{" "} + {transformSecondsToFormattedTime(videoDuration)} +
+ ))}
+ + + + + Report Issue + + + + + + + + Share Idea + + + diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx index 60cfddc..92e4c99 100644 --- a/src/components/app-sidebar.tsx +++ b/src/components/app-sidebar.tsx @@ -62,13 +62,13 @@ export function AppLeftSidebar() { + - - + diff --git a/src/components/color-theme/ColorThemePopoverButton.tsx b/src/components/color-theme/ColorThemePopoverButton.tsx index 711ebf9..dceea5f 100644 --- a/src/components/color-theme/ColorThemePopoverButton.tsx +++ b/src/components/color-theme/ColorThemePopoverButton.tsx @@ -19,6 +19,7 @@ import { DropdownMenuTrigger, } from "../ui/dropdown-menu"; import { useSidebar } from "../ui/sidebar"; +import { ResponsiveDropdown } from "../ui/responsive-dropdown"; function getCssVariable(name: string) { const value = window @@ -228,22 +229,14 @@ export function ColorThemeDropdownSidebar({ const { isMobile } = useSidebar(); return ( - - {children} - - -
- -
- -
- - - + + +
+ +
+ +
+ + ); } diff --git a/src/components/ui/drawer.tsx b/src/components/ui/drawer.tsx index 196db90..b4ae3ab 100644 --- a/src/components/ui/drawer.tsx +++ b/src/components/ui/drawer.tsx @@ -1,26 +1,24 @@ -"use client" +"use client"; -import * as React from "react" -import { Drawer as DrawerPrimitive } from "vaul" +import * as React from "react"; +import { Drawer as DrawerPrimitive } from "vaul"; -import { cn } from "~/lib/utils" +import { cn } from "~/lib/utils"; -const Drawer = ({ - shouldScaleBackground = true, - ...props -}: React.ComponentProps) => ( +export type DrawerProps = React.ComponentProps; +const Drawer = ({ shouldScaleBackground = true, ...props }: DrawerProps) => ( -) -Drawer.displayName = "Drawer" +); +Drawer.displayName = "Drawer"; -const DrawerTrigger = DrawerPrimitive.Trigger +const DrawerTrigger = DrawerPrimitive.Trigger; -const DrawerPortal = DrawerPrimitive.Portal +const DrawerPortal = DrawerPrimitive.Portal; -const DrawerClose = DrawerPrimitive.Close +const DrawerClose = DrawerPrimitive.Close; const DrawerOverlay = React.forwardRef< React.ElementRef, @@ -31,8 +29,8 @@ const DrawerOverlay = React.forwardRef< className={cn("fixed inset-0 z-50 bg-black/80", className)} {...props} /> -)) -DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName +)); +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName; const DrawerContent = React.forwardRef< React.ElementRef, @@ -43,17 +41,17 @@ const DrawerContent = React.forwardRef< -
+
{children} -)) -DrawerContent.displayName = "DrawerContent" +)); +DrawerContent.displayName = "DrawerContent"; const DrawerHeader = ({ className, @@ -63,8 +61,8 @@ const DrawerHeader = ({ className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)} {...props} /> -) -DrawerHeader.displayName = "DrawerHeader" +); +DrawerHeader.displayName = "DrawerHeader"; const DrawerFooter = ({ className, @@ -74,8 +72,8 @@ const DrawerFooter = ({ className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} /> -) -DrawerFooter.displayName = "DrawerFooter" +); +DrawerFooter.displayName = "DrawerFooter"; const DrawerTitle = React.forwardRef< React.ElementRef, @@ -84,13 +82,13 @@ const DrawerTitle = React.forwardRef< -)) -DrawerTitle.displayName = DrawerPrimitive.Title.displayName +)); +DrawerTitle.displayName = DrawerPrimitive.Title.displayName; const DrawerDescription = React.forwardRef< React.ElementRef, @@ -98,11 +96,11 @@ const DrawerDescription = React.forwardRef< >(({ className, ...props }, ref) => ( -)) -DrawerDescription.displayName = DrawerPrimitive.Description.displayName +)); +DrawerDescription.displayName = DrawerPrimitive.Description.displayName; export { Drawer, @@ -115,4 +113,4 @@ export { DrawerFooter, DrawerTitle, DrawerDescription, -} +}; diff --git a/src/components/ui/responsive-dialog.tsx b/src/components/ui/responsive-dialog.tsx deleted file mode 100644 index de9cfa2..0000000 --- a/src/components/ui/responsive-dialog.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import * as React from "react"; - -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "~/components/ui/dialog"; -import { - Drawer, - DrawerContent, - DrawerDescription, - DrawerHeader, - DrawerTitle, - DrawerTrigger, -} from "~/components/ui/drawer"; -import { useMediaQuery } from "~/lib/hooks/use-media-query"; - -interface ResponsiveDialogProps { - trigger: React.ReactNode; - children: React.ReactNode; - title?: string; - description?: string; -} -export function ResponsiveDialog({ - children, - trigger, - title, - description, -}: ResponsiveDialogProps) { - const [open, setOpen] = React.useState(false); - // const isDesktop = useMediaQuery("(min-width: 640px)"); - - if (true) { - return ( - - {trigger} - - - {title} - {description} - - {children} - - - ); - } - - return ( - - {trigger} - - - {title} - {description} - -
{children}
-
-
- ); -} diff --git a/src/components/ui/responsive-dropdown.tsx b/src/components/ui/responsive-dropdown.tsx new file mode 100644 index 0000000..14e9dea --- /dev/null +++ b/src/components/ui/responsive-dropdown.tsx @@ -0,0 +1,101 @@ +import * as React from "react"; + +import { + Drawer, + DrawerContent, + DrawerDescription, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "~/components/ui/drawer"; +import { useMediaQuery } from "~/lib/hooks/use-media-query"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger, +} from "./dropdown-menu"; +import { + DropdownMenuContentProps, + DropdownMenuItemProps, + DropdownMenuProps, +} from "@radix-ui/react-dropdown-menu"; + +export function ResponsiveDropdownMenuItem({ + children, + ...rest +}: DropdownMenuItemProps) { + const isDesktop = useMediaQuery("(min-width: 640px)"); + + if (isDesktop) { + return {children}; + } + + return children; +} + +export function ResponsiveDropdownLabel({ + children, + className, +}: { + children: React.ReactNode; + className?: string; +}) { + const isDesktop = useMediaQuery("(min-width: 640px)"); + + if (isDesktop) { + return ( + {children} + ); + } + + return
{children}
; +} + +interface ResponsiveDropdownProps { + trigger: React.ReactNode; + children: React.ReactNode; + title?: string; + description?: string; + side?: DropdownMenuContentProps["side"]; +} +export function ResponsiveDropdown({ + children, + trigger, + title, + description, + side, +}: ResponsiveDropdownProps) { + const [open, setOpen] = React.useState(false); + const isDesktop = useMediaQuery("(min-width: 640px)"); + + if (isDesktop) { + return ( + + {trigger} + + {children} + + + ); + } + + return ( + + {trigger} + + + {title} + {description} + +
{children}
+
+
+ ); +} diff --git a/src/content/releases/2025-04-18.md b/src/content/releases/2025-04-18.md new file mode 100644 index 0000000..fa753ca --- /dev/null +++ b/src/content/releases/2025-04-18.md @@ -0,0 +1,15 @@ +--- +title: "Quality of Life" +description: "Serial gets a variety of UI/UX improvements and a way to send feedback both good and bad" +publish_date: "2025-04-18" +public: true +--- + +#### Improvements + +- UI items in left sidebar are now always visible, even when the list of categories is long +- Adds "Report Issue" and "Share Idea" buttons to sidebar +- More intelligently sets date filter when selecting sidebar items: + - When selecting "All", sets the date to "Today" + - When selecting a feed, sets the date to "This Month" +- Adds nicer bottom sheet mobile UI for sidebar items with popup menus