Finish sort menu

This commit is contained in:
Zoe Roux 2023-03-15 12:09:44 +09:00
parent a716fd1e22
commit 33db01cbbf
4 changed files with 111 additions and 210 deletions

View File

@ -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>
);

View File

@ -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>
</>
);

View File

@ -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)}
// />
// )}
// </>
// );
};

View File

@ -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>
);
};