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
16 changes: 15 additions & 1 deletion src/components/settingItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
SelectValue,
SelectItem as _SelectItem,
} from "./ui/select";
import { Input } from "./ui/input";

type ItemBase = {
label: string;
Expand Down Expand Up @@ -123,4 +124,17 @@ function EntryItem({
);
}

export { Label, Description, Header, Item, SelectItem, SwitchItem, EntryItem };
function InputItem({
label,
description,
className,
...props
}: ItemBase & Omit<ComponentProps<typeof Input>, "className">) {
return (
<Item label={label} description={description} className={className}>
<Input {...props} className="w-48" />
</Item>
);
}

export { Label, Description, Header, Item, SelectItem, SwitchItem, EntryItem, InputItem };
24 changes: 24 additions & 0 deletions src/components/ui/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from "react";

import { cn } from "@/lib/utils";

export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;

const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
);
}
);
Input.displayName = "Input";

export { Input };
9 changes: 9 additions & 0 deletions src/features/header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IoLogoGithub } from "react-icons/io";
import { BiMenu } from "react-icons/bi";
import { ToggleButton } from "@/components/toggleButton";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { SettingMenu } from "../settingMenu";
import { useHeader } from "./viewModel";

Expand All @@ -18,6 +19,7 @@ export function Header() {
marqueeTitleState,
displayHistoryState,
filterState,
titleFilter,
isDesktop,
} = useHeader();
const cn = isScrolled ? "shadow-lg border-b" : "shadow-none";
Expand All @@ -35,6 +37,13 @@ export function Header() {
<div className="font-[Itim] text-2xl tracking-tighter text-primary hidden sm:block">
Vspo stream schedule
</div>
<Input
type="text"
placeholder="Filter by stream title..."
value={titleFilter.value}
onChange={(e) => titleFilter.onChange(e.target.value)}
className="max-w-xs ml-2 hidden md:block"
/>
<div className="ml-auto flex gap-2">
{isDesktop && (
<div>
Expand Down
13 changes: 12 additions & 1 deletion src/features/header/viewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useMediaQuery } from "react-responsive";

export function useHeader() {
const [isScrolled, setIsScrolled] = useState(false);
const { theme, isMarqueeTitle, isDisplayHistory, filteredStreamerIds } =
const { theme, isMarqueeTitle, isDisplayHistory, filteredStreamerIds, filteredTitle } =
useSettings();
const dispatch = useSettingDispatch();
const isDesktop = useMediaQuery({ query: "(min-width: 768px)" });
Expand Down Expand Up @@ -64,13 +64,24 @@ export function useHeader() {
description: "Filter by streamer",
};

const titleFilter = {
value: filteredTitle,
onChange: (value: string) => {
dispatch({
target: "filteredTitle",
payload: value,
});
},
};

return {
isScrolled,
onClickGithubIcon,
themeState,
marqueeTitleState,
displayHistoryState,
filterState,
titleFilter,
isDesktop,
};
}
3 changes: 2 additions & 1 deletion src/providers/setting/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const SettingSchema = z.object({
isMarqueeTitle: z.boolean().default(false),
isDisplayHistory: z.boolean().default(false),
filteredStreamerIds: z.array(z.string()).default([]),
filteredTitle: z.string().default(""),
});

export type Setting = z.infer<typeof SettingSchema>;
Expand All @@ -26,6 +27,6 @@ type ClearAction<K extends keyof Setting> = {
};

export type SettingAction =
| Action<"theme" | "isMarqueeTitle" | "isDisplayHistory">
| Action<"theme" | "isMarqueeTitle" | "isDisplayHistory" | "filteredTitle">
| FilterAction<"filteredStreamerIds">
| ClearAction<"filteredStreamerIds">;
14 changes: 12 additions & 2 deletions src/providers/vspoStream/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const parseToStreamer = (
export const VspoStreamProvider = ({ children }: { children: ReactNode }) => {
const [streamResponses, setStreamsResponse] = useState<StreamResponse[]>([]);
const [streamerMap, setStreamerMap] = useState<StreamerMap>({});
const { filteredStreamerIds } = useSettings();
const { filteredStreamerIds, filteredTitle } = useSettings();

useEffect(() => {
const streamCollectionName = import.meta.env.VITE_STREAM_COLLECTION_NAME;
Expand Down Expand Up @@ -97,6 +97,8 @@ export const VspoStreamProvider = ({ children }: { children: ReactNode }) => {
}, []);

const streams = useMemo<Stream[]>(() => {
const titleFilterLower = filteredTitle.trim().toLowerCase();

return streamResponses.reduce((results: Stream[], streamRes) => {
const channel = streamerMap[streamRes.streamerId][streamRes.platform];

Expand All @@ -113,9 +115,17 @@ export const VspoStreamProvider = ({ children }: { children: ReactNode }) => {
return results;
}

// filter by title
if (
titleFilterLower !== "" &&
!streamRes.title.toLowerCase().includes(titleFilterLower)
) {
return results;
}

return results.concat(parseToStream(streamRes, channel));
}, []);
}, [streamResponses, streamerMap, filteredStreamerIds]);
}, [streamResponses, streamerMap, filteredStreamerIds, filteredTitle]);

const streamers = useMemo<Streamer[]>(
() => Object.values(streamerMap),
Expand Down