mirror of
https://github.com/idanoo/autobrr
synced 2025-07-25 17:59:14 +00:00
feat: dark mode (#32)
This commit is contained in:
parent
974ca95d80
commit
66048c5533
49 changed files with 1736 additions and 1992 deletions
|
@ -1,5 +1,3 @@
|
|||
import React from "react";
|
||||
|
||||
interface props {
|
||||
text: string;
|
||||
buttonText?: string;
|
||||
|
@ -9,11 +7,11 @@ interface props {
|
|||
export function EmptyListState({ text, buttonText, buttonOnClick }: props) {
|
||||
return (
|
||||
<div className="px-4 py-12 flex flex-col items-center">
|
||||
<p className="text-center text-gray-500">{text}</p>
|
||||
<p className="text-center text-gray-500 dark:text-white">{text}</p>
|
||||
{buttonText && buttonOnClick && (
|
||||
<button
|
||||
type="button"
|
||||
className="relative inline-flex items-center px-4 py-2 mt-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
className="relative inline-flex items-center px-4 py-2 mt-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 dark:bg-blue-600 hover:bg-indigo-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
|
||||
onClick={buttonOnClick}
|
||||
>
|
||||
{buttonText}
|
||||
|
|
|
@ -199,7 +199,7 @@ function ListItem({ action, clients, filterID, idx }: ListItemProps) {
|
|||
enabledMutation.mutate(action.id);
|
||||
};
|
||||
|
||||
useEffect(() => {}, [action]);
|
||||
useEffect(() => { }, [action]);
|
||||
|
||||
const cancelButtonRef = useRef(null);
|
||||
|
||||
|
@ -358,7 +358,7 @@ function ListItem({ action, clients, filterID, idx }: ListItemProps) {
|
|||
onChange={toggleActive}
|
||||
className={classNames(
|
||||
action.enabled ? "bg-teal-500" : "bg-gray-200",
|
||||
"z-10 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-light-blue-500"
|
||||
"z-10 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"
|
||||
)}
|
||||
>
|
||||
<span className="sr-only">Use setting</span>
|
||||
|
@ -469,7 +469,7 @@ function ListItem({ action, clients, filterID, idx }: ListItemProps) {
|
|||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white border border-gray-300 rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-light-blue-500"
|
||||
className="bg-white border border-gray-300 rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import React from "react";
|
||||
|
||||
const DEBUG = ({ values }: any) => {
|
||||
if (process.env.NODE_ENV !== "development") {
|
||||
return null;
|
||||
|
@ -7,7 +5,7 @@ const DEBUG = ({ values }: any) => {
|
|||
|
||||
return (
|
||||
<div className="w-1/2 mx-auto mt-2 flex flex-col mt-12 mb-12">
|
||||
<pre className="mt-2">{JSON.stringify(values, 0 as any, 2)}</pre>
|
||||
<pre className="mt-2 dark:text-gray-500">{JSON.stringify(values, 0 as any, 2)}</pre>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {PlusIcon} from "@heroicons/react/solid";
|
||||
import { PlusIcon } from "@heroicons/react/solid";
|
||||
|
||||
interface props {
|
||||
title: string;
|
||||
|
@ -7,15 +7,15 @@ interface props {
|
|||
buttonAction: any;
|
||||
}
|
||||
|
||||
const EmptySimple = ({ title, subtitle, buttonText, buttonAction}: props) => (
|
||||
const EmptySimple = ({ title, subtitle, buttonText, buttonAction }: props) => (
|
||||
<div className="text-center py-8">
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">{title}</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">{subtitle}</p>
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900 dark:text-white">{title}</h3>
|
||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-200">{subtitle}</p>
|
||||
<div className="mt-6">
|
||||
<button
|
||||
type="button"
|
||||
onClick={buttonAction}
|
||||
className="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
className="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 dark:bg-blue-600 hover:bg-indigo-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
|
||||
>
|
||||
<PlusIcon className="-ml-1 mr-2 h-5 w-5" aria-hidden="true" />
|
||||
{buttonText}
|
||||
|
|
|
@ -7,8 +7,8 @@ interface Props {
|
|||
|
||||
const TitleSubtitle: React.FC<Props> = ({ title, subtitle }) => (
|
||||
<div>
|
||||
<h2 className="text-lg leading-6 font-medium text-gray-900">{title}</h2>
|
||||
<p className="mt-1 text-sm text-gray-500">{subtitle}</p>
|
||||
<h2 className="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">{title}</h2>
|
||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">{subtitle}</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Field } from "react-final-form";
|
||||
import React from "react";
|
||||
import Error from "./Error";
|
||||
import {classNames} from "../../styles/utils";
|
||||
import { classNames } from "../../styles/utils";
|
||||
|
||||
type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
||||
|
||||
|
@ -14,30 +14,30 @@ interface Props {
|
|||
autoComplete?: string;
|
||||
}
|
||||
|
||||
const PasswordField: React.FC<Props> = ({ name, label, placeholder, columns , className, autoComplete}) => (
|
||||
const PasswordField: React.FC<Props> = ({ name, label, placeholder, columns, className, autoComplete }) => (
|
||||
<div
|
||||
className={classNames(
|
||||
columns ? `col-span-${columns}` : "col-span-12"
|
||||
)}
|
||||
>
|
||||
{label && (
|
||||
<label htmlFor={name} className="block text-xs font-bold text-gray-700 uppercase tracking-wide">
|
||||
<label htmlFor={name} className="block text-xs font-bold text-gray-700 dark:text-white uppercase tracking-wide">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<Field
|
||||
name={name}
|
||||
render={({input, meta}) => (
|
||||
render={({ input }) => (
|
||||
<input
|
||||
{...input}
|
||||
id={name}
|
||||
type="password"
|
||||
autoComplete={autoComplete}
|
||||
className="mt-2 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-light-blue-500 focus:border-light-blue-500 sm:text-sm"
|
||||
className="mt-2 block w-full border border-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-white rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
/>
|
||||
<div>
|
||||
<Error name={name} classNames="text-red mt-2" />
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import {Switch} from "@headlessui/react";
|
||||
import {Field} from "react-final-form";
|
||||
import {classNames} from "../../styles/utils";
|
||||
import { Switch } from "@headlessui/react";
|
||||
import { Field } from "react-final-form";
|
||||
import { classNames } from "../../styles/utils";
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
|
@ -11,32 +11,32 @@ interface Props {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
const SwitchGroup: React.FC<Props> = ({name, label, description, defaultValue}) => (
|
||||
<ul className="mt-2 divide-y divide-gray-200">
|
||||
const SwitchGroup: React.FC<Props> = ({ name, label, description, defaultValue }) => (
|
||||
<ul className="mt-2 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<Switch.Group as="li" className="py-4 flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<Switch.Label as="p" className="text-sm font-medium text-gray-900"
|
||||
passive>
|
||||
<Switch.Label as="p" className="text-sm font-medium text-gray-900 dark:text-white"
|
||||
passive>
|
||||
{label}
|
||||
</Switch.Label>
|
||||
{description && (
|
||||
<Switch.Description className="text-sm text-gray-500">
|
||||
{description}
|
||||
</Switch.Description>
|
||||
<Switch.Description className="text-sm text-gray-500 dark:text-gray-700">
|
||||
{description}
|
||||
</Switch.Description>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Field
|
||||
name={name}
|
||||
defaultValue={defaultValue as any}
|
||||
render={({input: {onChange, checked, value}}) => (
|
||||
render={({ input: { onChange, checked, value } }) => (
|
||||
<Switch
|
||||
value={value}
|
||||
checked={value}
|
||||
onChange={onChange}
|
||||
className={classNames(
|
||||
value ? 'bg-teal-500' : 'bg-gray-200',
|
||||
'ml-4 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-light-blue-500'
|
||||
value ? 'bg-teal-500 dark:bg-blue-500' : 'bg-gray-200 dark:bg-gray-500',
|
||||
'ml-4 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'
|
||||
)}
|
||||
>
|
||||
<span className="sr-only">Use setting</span>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Field } from "react-final-form";
|
||||
import React from "react";
|
||||
import { Field } from "react-final-form";
|
||||
import Error from "./Error";
|
||||
import {classNames} from "../../styles/utils";
|
||||
import { classNames } from "../../styles/utils";
|
||||
|
||||
type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
||||
|
||||
|
@ -14,31 +14,31 @@ interface Props {
|
|||
autoComplete?: string;
|
||||
}
|
||||
|
||||
const TextField: React.FC<Props> = ({ name, label, placeholder, columns , className, autoComplete}) => (
|
||||
const TextField: React.FC<Props> = ({ name, label, placeholder, columns, className, autoComplete }) => (
|
||||
<div
|
||||
className={classNames(
|
||||
columns ? `col-span-${columns}` : "col-span-12"
|
||||
)}
|
||||
>
|
||||
{label && (
|
||||
<label htmlFor={name} className="block text-xs font-bold text-gray-700 uppercase tracking-wide">
|
||||
<label htmlFor={name} className="block text-xs font-bold text-gray-700 dark:text-white uppercase tracking-wide">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<Field
|
||||
name={name}
|
||||
render={({input, meta}) => (
|
||||
render={({ input }) => (
|
||||
<input
|
||||
{...input}
|
||||
id={name}
|
||||
type="text"
|
||||
value={input.value}
|
||||
autoComplete={autoComplete}
|
||||
className="mt-2 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-light-blue-500 focus:border-light-blue-500 sm:text-sm"
|
||||
className="mt-2 block w-full border border-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-white rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
/>
|
||||
<div>
|
||||
<Error name={name} classNames="text-red mt-2" />
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Field } from "react-final-form";
|
||||
import React from "react";
|
||||
import { Field } from "react-final-form";
|
||||
import Error from "./Error";
|
||||
import { classNames } from "../../styles/utils";
|
||||
|
||||
|
@ -14,12 +14,12 @@ interface Props {
|
|||
hidden?: boolean;
|
||||
}
|
||||
|
||||
const TextFieldWide: React.FC<Props> = ({ name, label, help, placeholder, defaultValue, required, hidden, className}) => (
|
||||
const TextFieldWide: React.FC<Props> = ({ name, label, help, placeholder, defaultValue, required, hidden, className }) => (
|
||||
<div hidden={hidden}
|
||||
className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
<div>
|
||||
|
||||
<label htmlFor={name} className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2">
|
||||
<label htmlFor={name} className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2">
|
||||
{label} {required && <span className="text-gray-500">*</span>}
|
||||
</label>
|
||||
</div>
|
||||
|
@ -32,7 +32,7 @@ const TextFieldWide: React.FC<Props> = ({ name, label, help, placeholder, defaul
|
|||
{...input}
|
||||
id={name}
|
||||
type="text"
|
||||
className={classNames(meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 focus:border-indigo-500 border-gray-300", "block w-full shadow-sm sm:text-sm rounded-md")}
|
||||
className={classNames(meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-700", "block w-full shadow-sm dark:bg-gray-800 sm:text-sm dark:text-white rounded-md")}
|
||||
placeholder={placeholder}
|
||||
hidden={hidden}
|
||||
/>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Field } from "react-final-form";
|
||||
import React from "react";
|
||||
import { Field } from "react-final-form";
|
||||
import Error from "../Error";
|
||||
import { classNames } from "../../../styles/utils";
|
||||
|
||||
|
@ -28,7 +28,7 @@ const NumberFieldWide: React.FC<Props> = ({
|
|||
<div>
|
||||
<label
|
||||
htmlFor={name}
|
||||
className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2"
|
||||
className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2"
|
||||
>
|
||||
{label} {required && <span className="text-gray-500">*</span>}
|
||||
</label>
|
||||
|
@ -46,15 +46,15 @@ const NumberFieldWide: React.FC<Props> = ({
|
|||
className={classNames(
|
||||
meta.touched && meta.error
|
||||
? "focus:ring-red-500 focus:border-red-500 border-red-500"
|
||||
: "focus:ring-indigo-500 focus:border-indigo-500 border-gray-300",
|
||||
"block w-full shadow-sm sm:text-sm rounded-md"
|
||||
: "focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-700",
|
||||
"block w-full shadow-sm dark:bg-gray-800 sm:text-sm dark:text-white rounded-md"
|
||||
)}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{help && (
|
||||
<p className="mt-2 text-sm text-gray-500" id={`${name}-description`}>{help}</p>
|
||||
<p className="mt-2 text-sm text-gray-500 dark:text-gray-200" id={`${name}-description`}>{help}</p>
|
||||
)}
|
||||
<Error name={name} classNames="block text-red-500 mt-2" />
|
||||
</div>
|
||||
|
|
|
@ -21,7 +21,7 @@ function PasswordField({ name, label, placeholder, defaultValue, help, required
|
|||
className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
|
||||
<div>
|
||||
|
||||
<label htmlFor={name} className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2">
|
||||
<label htmlFor={name} className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2">
|
||||
{label} {required && <span className="text-gray-500">*</span>}
|
||||
</label>
|
||||
</div>
|
||||
|
@ -35,7 +35,7 @@ function PasswordField({ name, label, placeholder, defaultValue, help, required
|
|||
{...input}
|
||||
id={name}
|
||||
type={isVisible ? "text" : "password"}
|
||||
className={classNames(meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 focus:border-indigo-500 border-gray-300", "block w-full shadow-sm sm:text-sm rounded-md")}
|
||||
className={classNames(meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-700", "block w-full dark:bg-gray-800 shadow-sm dark:text-gray-100 sm:text-sm rounded-md")}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 px-3 flex items-center" onClick={toggleVisibility}>
|
||||
|
|
|
@ -17,7 +17,7 @@ function RadioFieldsetWide({ name, legend, options }: props) {
|
|||
<fieldset>
|
||||
<div className="space-y-2 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:px-6 sm:py-5">
|
||||
<div>
|
||||
<legend className="text-sm font-medium text-gray-900">
|
||||
<legend className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{legend}
|
||||
</legend>
|
||||
</div>
|
||||
|
@ -31,7 +31,7 @@ function RadioFieldsetWide({ name, legend, options }: props) {
|
|||
<RadioGroup.Label className="sr-only">
|
||||
Privacy setting
|
||||
</RadioGroup.Label>
|
||||
<div className="bg-white rounded-md -space-y-px">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-md -space-y-px">
|
||||
{options.map((setting, settingIdx) => (
|
||||
<RadioGroup.Option
|
||||
key={setting.value}
|
||||
|
@ -45,8 +45,8 @@ function RadioFieldsetWide({ name, legend, options }: props) {
|
|||
? "rounded-bl-md rounded-br-md"
|
||||
: "",
|
||||
checked
|
||||
? "bg-indigo-50 border-indigo-200 z-10"
|
||||
: "border-gray-200",
|
||||
? "bg-indigo-50 dark:bg-gray-700 border-indigo-200 dark:border-blue-600 z-10"
|
||||
: "border-gray-200 dark:border-gray-700",
|
||||
"relative border p-4 flex cursor-pointer focus:outline-none"
|
||||
)
|
||||
}
|
||||
|
@ -56,10 +56,10 @@ function RadioFieldsetWide({ name, legend, options }: props) {
|
|||
<span
|
||||
className={classNames(
|
||||
checked
|
||||
? "bg-indigo-600 border-transparent"
|
||||
: "bg-white border-gray-300",
|
||||
? "bg-indigo-600 dark:bg-blue-600 border-transparent"
|
||||
: "bg-white border-gray-300 dark:border-gray-300",
|
||||
active
|
||||
? "ring-2 ring-offset-2 ring-indigo-500"
|
||||
? "ring-2 ring-offset-2 ring-indigo-500 dark:ring-blue-500"
|
||||
: "",
|
||||
"h-4 w-4 mt-0.5 cursor-pointer rounded-full border flex items-center justify-center"
|
||||
)}
|
||||
|
@ -71,7 +71,7 @@ function RadioFieldsetWide({ name, legend, options }: props) {
|
|||
<RadioGroup.Label
|
||||
as="span"
|
||||
className={classNames(
|
||||
checked ? "text-indigo-900" : "text-gray-900",
|
||||
checked ? "text-indigo-900 dark:text-blue-500" : "text-gray-900 dark:text-gray-300",
|
||||
"block text-sm font-medium"
|
||||
)}
|
||||
>
|
||||
|
@ -80,7 +80,7 @@ function RadioFieldsetWide({ name, legend, options }: props) {
|
|||
<RadioGroup.Description
|
||||
as="span"
|
||||
className={classNames(
|
||||
checked ? "text-indigo-700" : "text-gray-500",
|
||||
checked ? "text-indigo-700 dark:text-blue-500" : "text-gray-500",
|
||||
"block text-sm"
|
||||
)}
|
||||
>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {Fragment} from "react";
|
||||
import {Dialog, Transition} from "@headlessui/react";
|
||||
import {ExclamationIcon} from "@heroicons/react/solid";
|
||||
import { Fragment } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { ExclamationIcon } from "@heroicons/react/solid";
|
||||
|
||||
interface props {
|
||||
isOpen: boolean;
|
||||
|
@ -34,10 +34,9 @@ const DeleteModal = ({ isOpen, buttonRef, toggle, deleteAction, title, text }: p
|
|||
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
{/* This element is to trick the browser into centering the modal contents. */}
|
||||
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
|
||||
​
|
||||
</span>
|
||||
​
|
||||
</span>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
|
@ -48,24 +47,24 @@ const DeleteModal = ({ isOpen, buttonRef, toggle, deleteAction, title, text }: p
|
|||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
|
||||
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
<div className="bg-white dark:bg-gray-700 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
<div className="sm:flex sm:items-start">
|
||||
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<ExclamationIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
|
||||
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 dark:bg-red-400 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<ExclamationIcon className="h-6 w-6 text-red-600 dark:text-red-600" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<Dialog.Title as="h3" className="text-lg leading-6 font-medium text-gray-900">
|
||||
<Dialog.Title as="h3" className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
|
||||
{title}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-300">
|
||||
{text}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||
<div className="bg-gray-50 dark:bg-gray-800 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||
<button
|
||||
type="button"
|
||||
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
|
||||
|
@ -75,7 +74,7 @@ const DeleteModal = ({ isOpen, buttonRef, toggle, deleteAction, title, text }: p
|
|||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
|
||||
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 dark:border-gray-600 shadow-sm px-4 py-2 bg-white dark:bg-gray-700 text-base font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
|
||||
onClick={toggle}
|
||||
ref={buttonRef}
|
||||
>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { FC } from 'react'
|
||||
import { XIcon, CheckCircleIcon, ExclamationIcon, ExclamationCircleIcon } from '@heroicons/react/solid'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { classNames } from '../../styles/utils'
|
||||
|
||||
type Props = {
|
||||
type: 'error' | 'success' | 'warning'
|
||||
|
@ -14,9 +15,9 @@ const Toast: FC<Props> = ({
|
|||
t
|
||||
}) => {
|
||||
return (
|
||||
<div className={`${
|
||||
t.visible ? 'animate-enter' : 'animate-leave'
|
||||
} max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden transition-all`}>
|
||||
<div className={classNames(
|
||||
t.visible ? 'animate-enter' : 'animate-leave',
|
||||
"max-w-sm w-full bg-white dark:bg-gray-800 shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden transition-all")}>
|
||||
<div className="p-4">
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
|
@ -25,16 +26,16 @@ const Toast: FC<Props> = ({
|
|||
{type === 'warning' && <ExclamationIcon className="h-6 w-6 text-yellow-400" aria-hidden="true" />}
|
||||
</div>
|
||||
<div className="ml-3 w-0 flex-1 pt-0.5">
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-gray-200">
|
||||
{type === 'success' && "Success"}
|
||||
{type === 'error' && "Error"}
|
||||
{type === 'warning' && "Warning"}
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-gray-500">{body}</p>
|
||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">{body}</p>
|
||||
</div>
|
||||
<div className="ml-4 flex-shrink-0 flex">
|
||||
<button
|
||||
className="bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
className="bg-white dark:bg-gray-700 rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
|
||||
onClick={() => {
|
||||
toast.dismiss(t.id)
|
||||
}}
|
||||
|
|
140
web/src/components/panels/SlideOver.tsx
Normal file
140
web/src/components/panels/SlideOver.tsx
Normal file
|
@ -0,0 +1,140 @@
|
|||
import { Fragment, useRef } from "react";
|
||||
import { XIcon } from "@heroicons/react/solid";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Form } from "react-final-form";
|
||||
import DEBUG from "../../components/debug";
|
||||
import { useToggle } from "../../hooks/hooks";
|
||||
import { DeleteModal } from "../../components/modals";
|
||||
import { classNames } from "../../styles/utils";
|
||||
|
||||
interface props {
|
||||
title: string;
|
||||
initialValues: any;
|
||||
mutators?: any;
|
||||
validate?: any;
|
||||
onSubmit: any;
|
||||
isOpen: boolean;
|
||||
toggle: any;
|
||||
children?: (values: any) => React.ReactNode;
|
||||
deleteAction?: any
|
||||
type: "CREATE" | "UPDATE";
|
||||
}
|
||||
|
||||
function SlideOver({ title, initialValues, mutators, validate, onSubmit, deleteAction, isOpen, toggle, type, children }: props) {
|
||||
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false)
|
||||
|
||||
const cancelModalButtonRef = useRef(null)
|
||||
|
||||
return (
|
||||
<Transition.Root show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" static className="fixed inset-0 overflow-hidden" open={isOpen} onClose={toggle}>
|
||||
{deleteAction && (
|
||||
<DeleteModal
|
||||
isOpen={deleteModalIsOpen}
|
||||
toggle={toggleDeleteModal}
|
||||
buttonRef={cancelModalButtonRef}
|
||||
deleteAction={deleteAction}
|
||||
title={`Remove ${title}`}
|
||||
text={`Are you sure you want to remove this ${title}? This action cannot be undone.`}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="absolute inset-0 overflow-hidden">
|
||||
<Dialog.Overlay className="absolute inset-0" />
|
||||
|
||||
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="transform transition ease-in-out duration-500 sm:duration-700"
|
||||
enterFrom="translate-x-full"
|
||||
enterTo="translate-x-0"
|
||||
leave="transform transition ease-in-out duration-500 sm:duration-700"
|
||||
leaveFrom="translate-x-0"
|
||||
leaveTo="translate-x-full"
|
||||
>
|
||||
<div className="w-screen max-w-2xl dark:border-gray-700 border-l">
|
||||
|
||||
<Form
|
||||
initialValues={initialValues}
|
||||
mutators={mutators}
|
||||
onSubmit={onSubmit}
|
||||
validate={validate}
|
||||
>
|
||||
{({ handleSubmit, values }) => {
|
||||
return (
|
||||
<form className="h-full flex flex-col bg-white dark:bg-gray-800 shadow-xl overflow-y-scroll"
|
||||
onSubmit={handleSubmit}>
|
||||
|
||||
<div className="flex-1">
|
||||
<div className="px-4 py-6 bg-gray-50 dark:bg-gray-900 sm:px-6">
|
||||
<div className="flex items-start justify-between space-x-3">
|
||||
<div className="space-y-1">
|
||||
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">{type === "CREATE" ? "Create" : "Update"} {title}</Dialog.Title>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{type === "CREATE" ? "Create" : "Update"} {title}.
|
||||
</p>
|
||||
</div>
|
||||
<div className="h-7 flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white dark:bg-gray-900 rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
|
||||
onClick={toggle}
|
||||
>
|
||||
<span className="sr-only">Close panel</span>
|
||||
<XIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{children !== undefined && children(values)}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex-shrink-0 px-4 border-t border-gray-200 dark:border-gray-700 py-5 sm:px-6">
|
||||
<div className={classNames(type === "CREATE" ? "justify-end" : "justify-between", "space-x-3 flex")}>
|
||||
{type === "UPDATE" && (
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 dark:text-red-900 bg-red-100 dark:bg-red-500 hover:bg-red-200 dark:hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm"
|
||||
onClick={toggleDeleteModal}
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
)}
|
||||
<div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="bg-white dark:bg-gray-700 py-2 px-4 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-blue-500"
|
||||
onClick={toggle}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="ml-4 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 dark:bg-blue-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
{type === "CREATE" ? "Create" : "Save"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DEBUG values={values} />
|
||||
</form>
|
||||
)
|
||||
}}
|
||||
</Form>
|
||||
|
||||
</div>
|
||||
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export default SlideOver;
|
1
web/src/components/panels/index.ts
Normal file
1
web/src/components/panels/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as SlideOver } from "./SlideOver";
|
Loading…
Add table
Add a link
Reference in a new issue