mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Finish sort menu
This commit is contained in:
parent
a716fd1e22
commit
33db01cbbf
@ -27,10 +27,11 @@ import Close from "@material-symbols/svg-400/rounded/close-fill.svg";
|
||||
import { Icon, IconButton } from "./icons";
|
||||
import { PressableFeedback } from "./links";
|
||||
import { P } from "./text";
|
||||
import { ContrastArea } from "./themes";
|
||||
import { ContrastArea, SwitchVariant } from "./themes";
|
||||
import { ts } from "./utils";
|
||||
import Check from "@material-symbols/svg-400/rounded/check-fill.svg";
|
||||
import { useRouter } from "solito/router";
|
||||
import { SvgProps } from "react-native-svg";
|
||||
|
||||
const MenuContext = createContext<((open: boolean) => void) | undefined>(undefined);
|
||||
|
||||
@ -60,48 +61,50 @@ const Menu = <AsProps,>({
|
||||
{isOpen && (
|
||||
<Portal>
|
||||
<ContrastArea mode="user">
|
||||
{({ css, theme }) => (
|
||||
<MenuContext.Provider value={setOpen}>
|
||||
<Pressable
|
||||
onPress={() => setOpen(false)}
|
||||
focusable={false}
|
||||
{...css({ ...StyleSheet.absoluteFillObject, flexGrow: 1, bg: "transparent" })}
|
||||
/>
|
||||
<ScrollView
|
||||
{...css([
|
||||
{
|
||||
bg: (theme) => theme.background,
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
width: percent(100),
|
||||
alignSelf: "center",
|
||||
borderTopLeftRadius: px(26),
|
||||
borderTopRightRadius: { xs: px(26), xl: 0 },
|
||||
paddingTop: { xs: px(26), xl: 0 },
|
||||
marginTop: { xs: px(72), xl: 0 },
|
||||
},
|
||||
sm({
|
||||
maxWidth: px(640),
|
||||
marginHorizontal: px(56),
|
||||
}),
|
||||
xl({
|
||||
top: 0,
|
||||
right: 0,
|
||||
marginRight: 0,
|
||||
borderBottomLeftRadius: px(26),
|
||||
}),
|
||||
])}
|
||||
>
|
||||
<IconButton
|
||||
icon={Close}
|
||||
color={theme.colors.black}
|
||||
<SwitchVariant>
|
||||
{({ css, theme }) => (
|
||||
<MenuContext.Provider value={setOpen}>
|
||||
<Pressable
|
||||
onPress={() => setOpen(false)}
|
||||
{...css({ alignSelf: "flex-end", display: { xs: "none", xl: "flex" } })}
|
||||
focusable={false}
|
||||
{...css({ ...StyleSheet.absoluteFillObject, flexGrow: 1, bg: "transparent" })}
|
||||
/>
|
||||
{children}
|
||||
</ScrollView>
|
||||
</MenuContext.Provider>
|
||||
)}
|
||||
<ScrollView
|
||||
{...css([
|
||||
{
|
||||
bg: (theme) => theme.background,
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
width: percent(100),
|
||||
alignSelf: "center",
|
||||
borderTopLeftRadius: px(26),
|
||||
borderTopRightRadius: { xs: px(26), xl: 0 },
|
||||
paddingTop: { xs: px(26), xl: 0 },
|
||||
marginTop: { xs: px(72), xl: 0 },
|
||||
},
|
||||
sm({
|
||||
maxWidth: px(640),
|
||||
marginHorizontal: px(56),
|
||||
}),
|
||||
xl({
|
||||
top: 0,
|
||||
right: 0,
|
||||
marginRight: 0,
|
||||
borderBottomLeftRadius: px(26),
|
||||
}),
|
||||
])}
|
||||
>
|
||||
<IconButton
|
||||
icon={Close}
|
||||
color={theme.colors.black}
|
||||
onPress={() => setOpen(false)}
|
||||
{...css({ alignSelf: "flex-end", display: { xs: "none", xl: "flex" } })}
|
||||
/>
|
||||
{children}
|
||||
</ScrollView>
|
||||
</MenuContext.Provider>
|
||||
)}
|
||||
</SwitchVariant>
|
||||
</ContrastArea>
|
||||
</Portal>
|
||||
)}
|
||||
@ -114,10 +117,12 @@ const MenuItem = ({
|
||||
selected,
|
||||
onSelect,
|
||||
href,
|
||||
icon,
|
||||
...props
|
||||
}: {
|
||||
label: string;
|
||||
selected?: boolean;
|
||||
icon?: ComponentType<SvgProps>;
|
||||
} & ({ onSelect: () => void; href?: undefined } | { href: string; onSelect?: undefined })) => {
|
||||
const { css, theme } = useYoshiki();
|
||||
const setOpen = useContext(MenuContext);
|
||||
@ -141,7 +146,7 @@ const MenuItem = ({
|
||||
props as any,
|
||||
)}
|
||||
>
|
||||
{selected && <Icon icon={Check} color={theme.paragraph} size={24} />}
|
||||
{selected && <Icon icon={icon ?? Check} color={theme.paragraph} size={24} />}
|
||||
<P {...css({ paddingLeft: ts(2) + +!selected * px(24) })}>{label}</P>
|
||||
</PressableFeedback>
|
||||
);
|
||||
|
@ -25,10 +25,11 @@ import { PressableProps } from "react-native";
|
||||
import { useYoshiki } from "yoshiki/web";
|
||||
import { px, useYoshiki as useNativeYoshiki } from "yoshiki/native";
|
||||
import { P } from "./text";
|
||||
import { ContrastArea } from "./themes";
|
||||
import { ContrastArea, SwitchVariant } from "./themes";
|
||||
import { Icon } from "./icons";
|
||||
import Dot from "@material-symbols/svg-400/rounded/fiber_manual_record-fill.svg";
|
||||
import { focusReset } from "./utils";
|
||||
import { focusReset, ts } from "./utils";
|
||||
import { SvgProps } from "react-native-svg";
|
||||
|
||||
type YoshikiFunc<T> = (props: ReturnType<typeof useYoshiki>) => T;
|
||||
const YoshikiProvider = ({ children }: { children: YoshikiFunc<ReactNode> }) => {
|
||||
@ -68,26 +69,28 @@ const Menu = <AsProps extends { onPress: PressableProps["onPress"] }>({
|
||||
<InternalTriger Component={Trigger} ComponentProps={props} />
|
||||
</DropdownMenu.Trigger>
|
||||
<ContrastArea mode="user">
|
||||
<YoshikiProvider>
|
||||
{({ css }) => (
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
onFocusOutside={(e) => e.stopImmediatePropagation()}
|
||||
{...css({
|
||||
bg: (theme) => theme.background,
|
||||
overflow: "hidden",
|
||||
minWidth: "220px",
|
||||
borderRadius: "8px",
|
||||
boxShadow:
|
||||
"0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)",
|
||||
zIndex: 2,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
)}
|
||||
</YoshikiProvider>
|
||||
<SwitchVariant>
|
||||
<YoshikiProvider>
|
||||
{({ css }) => (
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
onFocusOutside={(e) => e.stopImmediatePropagation()}
|
||||
{...css({
|
||||
bg: (theme) => theme.background,
|
||||
overflow: "hidden",
|
||||
minWidth: "220px",
|
||||
borderRadius: "8px",
|
||||
boxShadow:
|
||||
"0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)",
|
||||
zIndex: 2,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
)}
|
||||
</YoshikiProvider>
|
||||
</SwitchVariant>
|
||||
</ContrastArea>
|
||||
</DropdownMenu.Root>
|
||||
);
|
||||
@ -122,7 +125,7 @@ const MenuItem = ({
|
||||
...props
|
||||
}: {
|
||||
label: string;
|
||||
icon?: JSX.Element;
|
||||
icon?: ComponentType<SvgProps>;
|
||||
selected?: boolean;
|
||||
} & ({ onSelect: () => void; href?: undefined } | { href: string; onSelect?: undefined })) => {
|
||||
const { css: nCss } = useNativeYoshiki();
|
||||
@ -132,7 +135,7 @@ const MenuItem = ({
|
||||
<>
|
||||
<style jsx global>{`
|
||||
[data-highlighted] {
|
||||
background: ${theme.alternate.accent};
|
||||
background: ${theme.variant.accent};
|
||||
}
|
||||
`}</style>
|
||||
<Item
|
||||
@ -151,13 +154,13 @@ const MenuItem = ({
|
||||
>
|
||||
{selected && (
|
||||
<Icon
|
||||
icon={Dot}
|
||||
icon={icon ?? Dot}
|
||||
color={theme.paragraph}
|
||||
size={px(8)}
|
||||
{...nCss({ paddingRight: px(8) })}
|
||||
size={ts(icon ? 2 : 1)}
|
||||
{...nCss({ paddingRight: ts(1) })}
|
||||
/>
|
||||
)}
|
||||
{<P {...nCss(!selected && { paddingLeft: px(8 * 2) })}>{label}</P>}
|
||||
{<P {...nCss(!selected && { paddingLeft: ts(1 + (icon ? 2 : 1)) })}>{label}</P>}
|
||||
</Item>
|
||||
</>
|
||||
);
|
||||
|
@ -24,82 +24,12 @@ import { useYoshiki } from "yoshiki/native";
|
||||
import GridView from "@material-symbols/svg-400/rounded/grid_view.svg";
|
||||
import ViewList from "@material-symbols/svg-400/rounded/view_list.svg";
|
||||
import Sort from "@material-symbols/svg-400/rounded/sort.svg";
|
||||
import ArrowUpward from "@material-symbols/svg-400/rounded/arrow_upward.svg";
|
||||
import ArrowDownward from "@material-symbols/svg-400/rounded/arrow_downward.svg";
|
||||
import { Layout, SortBy, SortOrd } from "./types";
|
||||
import { forwardRef } from "react";
|
||||
import { View, PressableProps } from "react-native";
|
||||
|
||||
// const SortByMenu = ({
|
||||
// sortKey,
|
||||
// setSort,
|
||||
// sortOrd,
|
||||
// setSortOrd,
|
||||
// anchor,
|
||||
// onClose,
|
||||
// }: {
|
||||
// sortKey: SortBy;
|
||||
// setSort: (sort: SortBy) => void;
|
||||
// sortOrd: SortOrd;
|
||||
// setSortOrd: (sort: SortOrd) => void;
|
||||
// anchor: HTMLElement;
|
||||
// onClose: () => void;
|
||||
// }) => {
|
||||
// const router = useRouter();
|
||||
// const { t } = useTranslation("browse");
|
||||
//
|
||||
// return (
|
||||
// <Menu
|
||||
// id="sortby-menu"
|
||||
// MenuListProps={{
|
||||
// "aria-labelledby": "sortby",
|
||||
// }}
|
||||
// anchorEl={anchor}
|
||||
// open={!!anchor}
|
||||
// onClose={onClose}
|
||||
// >
|
||||
// {Object.values(SortBy).map((x) => (
|
||||
// <MenuItem
|
||||
// key={x}
|
||||
// selected={sortKey === x}
|
||||
// onClick={() => setSort(x)}
|
||||
// component={Link}
|
||||
// to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }}
|
||||
// shallow
|
||||
// replace
|
||||
// >
|
||||
// <ListItemText>{t(`browse.sortkey.${x}`)}</ListItemText>
|
||||
// </MenuItem>
|
||||
// ))}
|
||||
// <Divider />
|
||||
// <MenuItem
|
||||
// selected={sortOrd === SortOrd.Asc}
|
||||
// onClick={() => setSortOrd(SortOrd.Asc)}
|
||||
// component={Link}
|
||||
// to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }}
|
||||
// shallow
|
||||
// replace
|
||||
// >
|
||||
// <ListItemIcon>
|
||||
// <South fontSize="small" />
|
||||
// </ListItemIcon>
|
||||
// <ListItemText>{t("browse.sortord.asc")}</ListItemText>
|
||||
// </MenuItem>
|
||||
// <MenuItem
|
||||
// selected={sortOrd === SortOrd.Desc}
|
||||
// onClick={() => setSortOrd(SortOrd.Desc)}
|
||||
// component={Link}
|
||||
// to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }}
|
||||
// shallow
|
||||
// replace
|
||||
// >
|
||||
// <ListItemIcon>
|
||||
// <North fontSize="small" />
|
||||
// </ListItemIcon>
|
||||
// <ListItemText>{t("browse.sortord.desc")}</ListItemText>
|
||||
// </MenuItem>
|
||||
// </Menu>
|
||||
// );
|
||||
// };
|
||||
|
||||
const SortTrigger = forwardRef<View, PressableProps & { sortKey: SortBy }>(function _SortTrigger(
|
||||
{ sortKey, ...props },
|
||||
ref,
|
||||
@ -121,20 +51,17 @@ const SortTrigger = forwardRef<View, PressableProps & { sortKey: SortBy }>(funct
|
||||
|
||||
export const BrowseSettings = ({
|
||||
sortKey,
|
||||
setSort,
|
||||
sortOrd,
|
||||
setSortOrd,
|
||||
setSort,
|
||||
layout,
|
||||
setLayout,
|
||||
}: {
|
||||
sortKey: SortBy;
|
||||
setSort: (sort: SortBy) => void;
|
||||
sortOrd: SortOrd;
|
||||
setSortOrd: (sort: SortOrd) => void;
|
||||
setSort: (sort: SortBy, ord: SortOrd) => void;
|
||||
layout: Layout;
|
||||
setLayout: (layout: Layout) => void;
|
||||
}) => {
|
||||
// const [sortAnchor, setSortAnchor] = useState<HTMLElement | null>(null);
|
||||
const { css, theme } = useYoshiki();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -148,10 +75,10 @@ export const BrowseSettings = ({
|
||||
key={x}
|
||||
label={t(`browse.sortkey.${x}`)}
|
||||
selected={sortKey === x}
|
||||
onSelect={() => setSort(x)}
|
||||
// component={Link}
|
||||
// to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }}
|
||||
// TODO: Set query param for sort.
|
||||
icon={sortOrd === SortOrd.Asc ? ArrowUpward : ArrowDownward}
|
||||
onSelect={() =>
|
||||
setSort(x, sortKey === x && sortOrd === SortOrd.Asc ? SortOrd.Desc : SortOrd.Asc)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Menu>
|
||||
@ -173,40 +100,4 @@ export const BrowseSettings = ({
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
// return (
|
||||
// <>
|
||||
// <Box sx={{ display: "flex", justifyContent: "space-around" }}>
|
||||
// <ButtonGroup sx={{ m: 1 }}>
|
||||
// <Button disabled>
|
||||
// <FilterList />
|
||||
// </Button>
|
||||
// <Tooltip title={t("browse.sortby-tt")}>
|
||||
// <Button
|
||||
// id="sortby"
|
||||
// aria-label={t("browse.sortby-tt")}
|
||||
// aria-controls={sortAnchor ? "sorby-menu" : undefined}
|
||||
// aria-haspopup="true"
|
||||
// aria-expanded={sortAnchor ? "true" : undefined}
|
||||
// onClick={(event) => setSortAnchor(event.currentTarget)}
|
||||
// >
|
||||
// <Sort />
|
||||
// {t("browse.sortby", { key: t(`browse.sortkey.${sortKey}`) })}
|
||||
// {sortOrd === SortOrd.Asc ? <South fontSize="small" /> : <North fontSize="small" />}
|
||||
// </Button>
|
||||
// </Tooltip>
|
||||
// </ButtonGroup>
|
||||
// </Box>
|
||||
// {sortAnchor && (
|
||||
// <SortByMenu
|
||||
// sortKey={sortKey}
|
||||
// sortOrd={sortOrd}
|
||||
// setSort={setSort}
|
||||
// setSortOrd={setSortOrd}
|
||||
// anchor={sortAnchor}
|
||||
// onClose={() => setSortAnchor(null)}
|
||||
// />
|
||||
// )}
|
||||
// </>
|
||||
// );
|
||||
};
|
||||
|
@ -18,7 +18,6 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ComponentProps, useState } from "react";
|
||||
import {
|
||||
QueryIdentifier,
|
||||
QueryPage,
|
||||
@ -27,6 +26,8 @@ import {
|
||||
ItemType,
|
||||
getDisplayDate,
|
||||
} from "@kyoo/models";
|
||||
import { ComponentProps, useState } from "react";
|
||||
import { createParam } from "solito";
|
||||
import { DefaultLayout } from "../layout";
|
||||
import { WithLoading } from "../fetch";
|
||||
import { InfiniteFetch } from "../fetch-infinite";
|
||||
@ -35,6 +36,8 @@ import { ItemList } from "./list";
|
||||
import { SortBy, SortOrd, Layout } from "./types";
|
||||
import { BrowseSettings } from "./header";
|
||||
|
||||
const { useParam } = createParam<{ sortBy?: string }>();
|
||||
|
||||
export const itemMap = (
|
||||
item: WithLoading<LibraryItem>,
|
||||
): WithLoading<ComponentProps<typeof ItemGrid> & ComponentProps<typeof ItemList>> => {
|
||||
@ -72,33 +75,32 @@ const query = (
|
||||
});
|
||||
|
||||
export const BrowsePage: QueryPage<{ slug?: string }> = ({ slug }) => {
|
||||
const [sortKey, setSort] = useState(SortBy.Name);
|
||||
const [sortOrd, setSortOrd] = useState(SortOrd.Asc);
|
||||
const [sort, setSort] = useParam("sortBy");
|
||||
const sortKey = (sort?.split(":")[0] as SortBy) || SortBy.Name;
|
||||
const sortOrd = (sort?.split(":")[1] as SortOrd) || SortOrd.Asc;
|
||||
const [layout, setLayout] = useState(Layout.Grid);
|
||||
|
||||
const LayoutComponent = layout === Layout.Grid ? ItemGrid : ItemList;
|
||||
|
||||
// TODO list header to seet sort things, filter and layout.
|
||||
return (
|
||||
<>
|
||||
<InfiniteFetch
|
||||
query={query(slug, sortKey, sortOrd)}
|
||||
placeholderCount={15}
|
||||
layout={LayoutComponent.layout}
|
||||
Header={
|
||||
<BrowseSettings
|
||||
sortKey={sortKey}
|
||||
setSort={setSort}
|
||||
sortOrd={sortOrd}
|
||||
setSortOrd={setSortOrd}
|
||||
layout={layout}
|
||||
setLayout={setLayout}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{(item) => <LayoutComponent {...itemMap(item)} />}
|
||||
</InfiniteFetch>
|
||||
</>
|
||||
<InfiniteFetch
|
||||
query={query(slug, sortKey, sortOrd)}
|
||||
placeholderCount={15}
|
||||
layout={LayoutComponent.layout}
|
||||
Header={
|
||||
<BrowseSettings
|
||||
sortKey={sortKey}
|
||||
sortOrd={sortOrd}
|
||||
setSort={(key, ord) => {
|
||||
setSort(`${key}:${ord}`);
|
||||
}}
|
||||
layout={layout}
|
||||
setLayout={setLayout}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{(item) => <LayoutComponent {...itemMap(item)} />}
|
||||
</InfiniteFetch>
|
||||
);
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user