mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
refactor(ui): updated modal design with backdrops
This commit is contained in:
@@ -6,6 +6,7 @@ import { useLockBodyScroll } from '../../../hooks/useLockBodyScroll';
|
|||||||
import globalMessages from '../../../i18n/globalMessages';
|
import globalMessages from '../../../i18n/globalMessages';
|
||||||
import Transition from '../../Transition';
|
import Transition from '../../Transition';
|
||||||
import Button, { ButtonType } from '../Button';
|
import Button, { ButtonType } from '../Button';
|
||||||
|
import CachedImage from '../CachedImage';
|
||||||
import LoadingSpinner from '../LoadingSpinner';
|
import LoadingSpinner from '../LoadingSpinner';
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
@@ -29,6 +30,7 @@ interface ModalProps {
|
|||||||
backgroundClickable?: boolean;
|
backgroundClickable?: boolean;
|
||||||
iconSvg?: ReactNode;
|
iconSvg?: ReactNode;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
backdrop?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Modal: React.FC<ModalProps> = ({
|
const Modal: React.FC<ModalProps> = ({
|
||||||
@@ -53,6 +55,7 @@ const Modal: React.FC<ModalProps> = ({
|
|||||||
tertiaryDisabled = false,
|
tertiaryDisabled = false,
|
||||||
tertiaryText,
|
tertiaryText,
|
||||||
onTertiary,
|
onTertiary,
|
||||||
|
backdrop,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const modalRef = useRef<HTMLDivElement>(null);
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -66,7 +69,7 @@ const Modal: React.FC<ModalProps> = ({
|
|||||||
return ReactDOM.createPortal(
|
return ReactDOM.createPortal(
|
||||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||||
<div
|
<div
|
||||||
className="fixed top-0 bottom-0 left-0 right-0 z-50 flex items-center justify-center w-full h-full bg-gray-800 bg-opacity-50"
|
className="fixed top-0 bottom-0 left-0 right-0 z-50 flex items-center justify-center w-full h-full bg-gray-800 bg-opacity-70"
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
typeof onCancel === 'function' && backgroundClickable
|
typeof onCancel === 'function' && backgroundClickable
|
||||||
@@ -98,7 +101,7 @@ const Modal: React.FC<ModalProps> = ({
|
|||||||
show={!loading}
|
show={!loading}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="relative inline-block w-full px-4 pt-5 pb-4 overflow-auto text-left align-bottom transition-all transform bg-gray-700 shadow-xl sm:rounded-lg sm:my-8 sm:align-middle sm:max-w-3xl"
|
className="relative inline-block w-full px-4 pt-5 pb-4 overflow-auto text-left align-bottom transition-all transform bg-gray-700 shadow-xl ring-1 ring-gray-500 sm:rounded-lg sm:my-8 sm:align-middle sm:max-w-3xl"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby="modal-headline"
|
aria-labelledby="modal-headline"
|
||||||
@@ -107,7 +110,25 @@ const Modal: React.FC<ModalProps> = ({
|
|||||||
maxHeight: 'calc(100% - env(safe-area-inset-top) * 2)',
|
maxHeight: 'calc(100% - env(safe-area-inset-top) * 2)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="sm:flex sm:items-center">
|
{backdrop && (
|
||||||
|
<div className="absolute top-0 left-0 right-0 z-0 w-full h-64">
|
||||||
|
<CachedImage
|
||||||
|
alt=""
|
||||||
|
src={backdrop}
|
||||||
|
layout="fill"
|
||||||
|
objectFit="cover"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="absolute inset-0"
|
||||||
|
style={{
|
||||||
|
backgroundImage:
|
||||||
|
'linear-gradient(180deg, rgba(55, 65, 81, 0.85) 0%, rgba(55, 65, 81, 1) 100%)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="relative sm:flex sm:items-center">
|
||||||
{iconSvg && <div className="modal-icon">{iconSvg}</div>}
|
{iconSvg && <div className="modal-icon">{iconSvg}</div>}
|
||||||
<div
|
<div
|
||||||
className={`mt-3 text-center sm:mt-0 sm:text-left ${
|
className={`mt-3 text-center sm:mt-0 sm:text-left ${
|
||||||
@@ -125,12 +146,12 @@ const Modal: React.FC<ModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{children && (
|
{children && (
|
||||||
<div className="mt-4 text-sm leading-5 text-gray-300">
|
<div className="relative mt-4 text-sm leading-5 text-gray-300">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(onCancel || onOk || onSecondary || onTertiary) && (
|
{(onCancel || onOk || onSecondary || onTertiary) && (
|
||||||
<div className="flex flex-row-reverse justify-center mt-5 sm:mt-4 sm:justify-start">
|
<div className="relative flex flex-row-reverse justify-center mt-5 sm:mt-4 sm:justify-start">
|
||||||
{typeof onOk === 'function' && (
|
{typeof onOk === 'function' && (
|
||||||
<Button
|
<Button
|
||||||
buttonType={okButtonType}
|
buttonType={okButtonType}
|
||||||
|
@@ -44,7 +44,7 @@ const SlideOver: React.FC<SlideOverProps> = ({
|
|||||||
>
|
>
|
||||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||||
<div
|
<div
|
||||||
className={`z-50 fixed inset-0 overflow-hidden bg-opacity-50 bg-gray-800`}
|
className={`z-50 fixed inset-0 overflow-hidden bg-opacity-70 bg-gray-800`}
|
||||||
onClick={() => onClose()}
|
onClick={() => onClose()}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
|
@@ -237,6 +237,7 @@ const MovieRequestModal: React.FC<RequestModalProps> = ({
|
|||||||
secondaryButtonType="danger"
|
secondaryButtonType="danger"
|
||||||
cancelText={intl.formatMessage(globalMessages.close)}
|
cancelText={intl.formatMessage(globalMessages.close)}
|
||||||
iconSvg={<DownloadIcon />}
|
iconSvg={<DownloadIcon />}
|
||||||
|
backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`}
|
||||||
>
|
>
|
||||||
{isOwner
|
{isOwner
|
||||||
? intl.formatMessage(messages.pendingapproval)
|
? intl.formatMessage(messages.pendingapproval)
|
||||||
@@ -295,6 +296,7 @@ const MovieRequestModal: React.FC<RequestModalProps> = ({
|
|||||||
}
|
}
|
||||||
okButtonType={'primary'}
|
okButtonType={'primary'}
|
||||||
iconSvg={<DownloadIcon />}
|
iconSvg={<DownloadIcon />}
|
||||||
|
backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`}
|
||||||
>
|
>
|
||||||
{hasAutoApprove && !quota?.movie.restricted && (
|
{hasAutoApprove && !quota?.movie.restricted && (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
|
@@ -420,6 +420,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
|||||||
: intl.formatMessage(globalMessages.cancel)
|
: intl.formatMessage(globalMessages.cancel)
|
||||||
}
|
}
|
||||||
iconSvg={<DownloadIcon />}
|
iconSvg={<DownloadIcon />}
|
||||||
|
backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`}
|
||||||
>
|
>
|
||||||
{editRequest
|
{editRequest
|
||||||
? isOwner
|
? isOwner
|
||||||
|
@@ -79,7 +79,7 @@ const ServerInstance: React.FC<ServerInstanceProps> = ({
|
|||||||
const serviceUrl = externalUrl ?? internalUrl;
|
const serviceUrl = externalUrl ?? internalUrl;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="col-span-1 bg-gray-700 rounded-lg shadow">
|
<li className="col-span-1 bg-gray-800 rounded-lg shadow ring-1 ring-gray-500">
|
||||||
<div className="flex items-center justify-between w-full p-6 space-x-6">
|
<div className="flex items-center justify-between w-full p-6 space-x-6">
|
||||||
<div className="flex-1 truncate">
|
<div className="flex-1 truncate">
|
||||||
<div className="flex items-center mb-2 space-x-2">
|
<div className="flex items-center mb-2 space-x-2">
|
||||||
@@ -134,9 +134,9 @@ const ServerInstance: React.FC<ServerInstanceProps> = ({
|
|||||||
)}
|
)}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-t border-gray-800">
|
<div className="border-t border-gray-500">
|
||||||
<div className="flex -mt-px">
|
<div className="flex -mt-px">
|
||||||
<div className="flex flex-1 w-0 border-r border-gray-800">
|
<div className="flex flex-1 w-0 border-r border-gray-500">
|
||||||
<button
|
<button
|
||||||
onClick={() => onEdit()}
|
onClick={() => onEdit()}
|
||||||
className="relative inline-flex items-center justify-center flex-1 w-0 py-4 -mr-px text-sm font-medium leading-5 text-gray-200 transition duration-150 ease-in-out border border-transparent rounded-bl-lg hover:text-white focus:outline-none focus:ring-blue focus:border-gray-500 focus:z-10"
|
className="relative inline-flex items-center justify-center flex-1 w-0 py-4 -mr-px text-sm font-medium leading-5 text-gray-200 transition duration-150 ease-in-out border border-transparent rounded-bl-lg hover:text-white focus:outline-none focus:ring-blue focus:border-gray-500 focus:z-10"
|
||||||
|
@@ -302,7 +302,7 @@ button.input-action svg,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-icon {
|
.modal-icon {
|
||||||
@apply flex items-center justify-center flex-shrink-0 w-12 h-12 mx-auto text-white bg-gray-600 rounded-full sm:mx-0 sm:h-10 sm:w-10;
|
@apply flex items-center justify-center flex-shrink-0 w-12 h-12 mx-auto text-white bg-gray-800 rounded-full ring-1 ring-gray-500 sm:mx-0 sm:h-10 sm:w-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-icon svg {
|
.modal-icon svg {
|
||||||
|
Reference in New Issue
Block a user