fix(web): mobile ux improvements (#296)

* fix(ListboxFilter): Added z-index in order to properly render dropdown box.
fix(ReleaseTable): Added flex-direction: column to wrap on mobile devices (otherwise it's set to row)
feat(DownloadClientSettingsListItem): Made it possible to toggle client state directly, without opening the update form.

* fix(TitleCell): Improved responsiveness across all relevant screen selectors.

* fix(FilterDetails): Fixed incorrect overflow property ordering.
This commit is contained in:
stacksmash76 2022-06-11 19:11:42 +02:00 committed by GitHub
parent bb3ea6ff18
commit 4677057bee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 105 additions and 84 deletions

View file

@ -17,7 +17,7 @@ export const AgeCell = ({ value }: CellProps) => (
export const TitleCell = ({ value }: CellProps) => (
<div
className="text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[128px] sm:max-w-none overflow-auto py-4"
className="text-sm font-medium box-content text-gray-900 dark:text-gray-300 max-w-[128px] sm:max-w-[256px] md:max-w-[360px] lg:max-w-[640px] xl:max-w-[840px] overflow-auto py-4"
title={value}
>
{value}

View file

@ -223,84 +223,82 @@ export default function FilterDetails() {
</header>
<div className="max-w-screen-xl mx-auto pb-12 px-4 sm:px-6 lg:px-8">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow">
<div className="relative mx-auto md:px-6 xl:px-4">
<div className="pt-1 pb-6 block overflow-auto">
<div className="border-b border-gray-200 dark:border-gray-700">
<nav className="-mb-px flex space-x-6 sm:space-x-8">
{tabs.map((tab) => (
<TabNavLink item={tab} key={tab.href} />
))}
</nav>
</div>
<Formik
initialValues={{
id: filter.id,
name: filter.name,
enabled: filter.enabled || false,
min_size: filter.min_size,
max_size: filter.max_size,
delay: filter.delay,
priority: filter.priority,
max_downloads: filter.max_downloads,
max_downloads_unit: filter.max_downloads_unit,
use_regex: filter.use_regex || false,
shows: filter.shows,
years: filter.years,
resolutions: filter.resolutions || [],
sources: filter.sources || [],
codecs: filter.codecs || [],
containers: filter.containers || [],
match_hdr: filter.match_hdr || [],
except_hdr: filter.except_hdr || [],
match_other: filter.match_other || [],
except_other: filter.except_other || [],
seasons: filter.seasons,
episodes: filter.episodes,
match_releases: filter.match_releases,
except_releases: filter.except_releases,
match_release_groups: filter.match_release_groups,
except_release_groups: filter.except_release_groups,
match_categories: filter.match_categories,
except_categories: filter.except_categories,
tags: filter.tags,
except_tags: filter.except_tags,
match_uploaders: filter.match_uploaders,
except_uploaders: filter.except_uploaders,
freeleech: filter.freeleech,
freeleech_percent: filter.freeleech_percent,
formats: filter.formats || [],
quality: filter.quality || [],
media: filter.media || [],
match_release_types: filter.match_release_types || [],
log_score: filter.log_score,
log: filter.log,
cue: filter.cue,
perfect_flac: filter.perfect_flac,
artists: filter.artists,
albums: filter.albums,
origins: filter.origins || [],
indexers: filter.indexers || [],
actions: filter.actions || []
} as Filter}
onSubmit={handleSubmit}
>
{({ values, dirty, resetForm }) => (
<Form>
<Routes>
<Route index element={<General />} />
<Route path="movies-tv" element={<MoviesTv />} />
<Route path="music" element={<Music />} />
<Route path="advanced" element={<Advanced />} />
<Route path="actions" element={<FilterActions filter={filter} values={values} />}
/>
</Routes>
<FormButtonsGroup values={values} deleteAction={deleteAction} dirty={dirty} reset={resetForm} />
<DEBUG values={values} />
</Form>
)}
</Formik>
<div className="pt-1 px-4 pb-6 block">
<div className="border-b border-gray-200 dark:border-gray-700">
<nav className="-mb-px flex space-x-6 sm:space-x-8 overflow-x-auto">
{tabs.map((tab) => (
<TabNavLink item={tab} key={tab.href} />
))}
</nav>
</div>
<Formik
initialValues={{
id: filter.id,
name: filter.name,
enabled: filter.enabled || false,
min_size: filter.min_size,
max_size: filter.max_size,
delay: filter.delay,
priority: filter.priority,
max_downloads: filter.max_downloads,
max_downloads_unit: filter.max_downloads_unit,
use_regex: filter.use_regex || false,
shows: filter.shows,
years: filter.years,
resolutions: filter.resolutions || [],
sources: filter.sources || [],
codecs: filter.codecs || [],
containers: filter.containers || [],
match_hdr: filter.match_hdr || [],
except_hdr: filter.except_hdr || [],
match_other: filter.match_other || [],
except_other: filter.except_other || [],
seasons: filter.seasons,
episodes: filter.episodes,
match_releases: filter.match_releases,
except_releases: filter.except_releases,
match_release_groups: filter.match_release_groups,
except_release_groups: filter.except_release_groups,
match_categories: filter.match_categories,
except_categories: filter.except_categories,
tags: filter.tags,
except_tags: filter.except_tags,
match_uploaders: filter.match_uploaders,
except_uploaders: filter.except_uploaders,
freeleech: filter.freeleech,
freeleech_percent: filter.freeleech_percent,
formats: filter.formats || [],
quality: filter.quality || [],
media: filter.media || [],
match_release_types: filter.match_release_types || [],
log_score: filter.log_score,
log: filter.log,
cue: filter.cue,
perfect_flac: filter.perfect_flac,
artists: filter.artists,
albums: filter.albums,
origins: filter.origins || [],
indexers: filter.indexers || [],
actions: filter.actions || []
} as Filter}
onSubmit={handleSubmit}
>
{({ values, dirty, resetForm }) => (
<Form>
<Routes>
<Route index element={<General />} />
<Route path="movies-tv" element={<MoviesTv />} />
<Route path="music" element={<Music />} />
<Route path="advanced" element={<Advanced />} />
<Route path="actions" element={<FilterActions filter={filter} values={values} />}
/>
</Routes>
<FormButtonsGroup values={values} deleteAction={deleteAction} dirty={dirty} reset={resetForm} />
<DEBUG values={values} />
</Form>
)}
</Formik>
</div>
</div>
</div>

View file

@ -49,7 +49,7 @@ const ListboxFilter = ({
leaveTo="opacity-0"
>
<Listbox.Options
className="absolute w-full mt-1 overflow-auto text-base bg-white dark:bg-gray-800 rounded-md shadow-lg max-h-60 border border-opacity-5 border-black dark:border-gray-700 dark:border-opacity-40 focus:outline-none sm:text-sm"
className="absolute z-10 w-full mt-1 overflow-auto text-base bg-white dark:bg-gray-800 rounded-md shadow-lg max-h-60 border border-opacity-5 border-black dark:border-gray-700 dark:border-opacity-40 focus:outline-none sm:text-sm"
>
<FilterOption label="All" />
{children}

View file

@ -181,7 +181,7 @@ export const ReleaseTable = () => {
// Render the UI for your table
return (
<div className="flex flex-col">
<div className="flex mb-6">
<div className="flex mb-6 flex-col sm:flex-row">
{headerGroups.map((headerGroup) =>
headerGroup.headers.map((column) => (
column.Filter ? (

View file

@ -1,11 +1,13 @@
import { useToggle } from "../../hooks/hooks";
import { Switch } from "@headlessui/react";
import { useQuery } from "react-query";
import { useMutation, useQuery, useQueryClient } from "react-query";
import { classNames } from "../../utils";
import { DownloadClientAddForm, DownloadClientUpdateForm } from "../../forms";
import { EmptySimple } from "../../components/emptystates";
import { APIClient } from "../../api/APIClient";
import { DownloadClientTypeNameMap } from "../../domain/constants";
import toast from "react-hot-toast";
import Toast from "../../components/notifications/Toast";
interface DLSettingsItemProps {
client: DownloadClient;
@ -15,14 +17,35 @@ interface DLSettingsItemProps {
function DownloadClientSettingsListItem({ client, idx }: DLSettingsItemProps) {
const [updateClientIsOpen, toggleUpdateClient] = useToggle(false);
const queryClient = useQueryClient();
const mutation = useMutation(
(client: DownloadClient) => APIClient.download_clients.update(client),
{
onSuccess: () => {
queryClient.invalidateQueries(["downloadClients"]);
toast.custom((t) => <Toast type="success" body={`${client.name} was updated successfully`} t={t}/>);
}
}
);
const onToggleMutation = (newState: boolean) => {
mutation.mutate({
...client,
enabled: newState
});
};
return (
<tr key={client.name} className={idx % 2 === 0 ? "light:bg-white" : "light:bg-gray-50"}>
<DownloadClientUpdateForm client={client} isOpen={updateClientIsOpen} toggle={toggleUpdateClient} />
<DownloadClientUpdateForm
client={client}
isOpen={updateClientIsOpen}
toggle={toggleUpdateClient}
/>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<Switch
checked={client.enabled}
onChange={toggleUpdateClient}
onChange={onToggleMutation}
className={classNames(
client.enabled ? "bg-teal-500 dark:bg-blue-500" : "bg-gray-200 dark:bg-gray-600",
"relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"