feat: add webui

This commit is contained in:
Ludvig Lundgren 2021-08-11 15:27:48 +02:00
parent a838d994a6
commit 773e57afe6
59 changed files with 19794 additions and 0 deletions

View file

@ -0,0 +1,20 @@
import React from "react";
import { Field } from "react-final-form";
interface Props {
name: string;
classNames?: string;
subscribe?: any;
}
const Error: React.FC<Props> = ({ name, classNames }) => (
<Field
name={name}
subscribe={{ touched: true, error: true }}
render={({ meta: { touched, error } }) =>
touched && error ? <span className={classNames}>{error}</span> : null
}
/>
);
export default Error;

View file

@ -0,0 +1,50 @@
import React from "react";
import {Field} from "react-final-form";
import MultiSelect from "react-multi-select-component";
import {classNames, COL_WIDTHS} from "../../styles/utils";
interface Props {
label?: string;
options?: [] | any;
name: string;
className?: string;
columns?: COL_WIDTHS;
}
const MultiSelectField: React.FC<Props> = ({
name,
label,
options,
className,
columns
}) => (
<div
className={classNames(
columns ? `col-span-${columns}` : "col-span-12"
)}
>
<label
className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
htmlFor={label}
>
{label}
</label>
<Field
name={name}
parse={val => val && val.map((item: any) => item.value)}
format={val =>
val &&
val.map((item: any) => options.find((o: any) => o.value === item))
}
render={({input, meta}) => (
<MultiSelect
{...input}
options={options}
labelledBy={name}
/>
)}
/>
</div>
);
export default MultiSelectField;

View file

@ -0,0 +1,60 @@
import React from "react";
import {Field} from "react-final-form";
export interface radioFieldsetOption {
label: string;
description: string;
value: string;
}
interface props {
name: string;
legend: string;
options: radioFieldsetOption[];
}
const RadioFieldset: React.FC<props> = ({ name, legend,options }) => (
<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}</legend>
</div>
<div className="space-y-5 sm:col-span-2">
<div className="space-y-5 sm:mt-0">
{options.map((opt, idx) => (
<div className="relative flex items-start" key={idx}>
<div className="absolute flex items-center h-5">
<Field
name={name}
type="radio"
render={({input}) => (
<input
{...input}
id={name}
value={opt.value}
// type="radio"
checked={input.checked}
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
/>
)}
/>
</div>
<div className="pl-7 text-sm">
<label htmlFor={opt.value} className="font-medium text-gray-900">
{opt.label}
</label>
<p id={opt.value+"_description"} className="text-gray-500">
{opt.description}
</p>
</div>
</div>
))}
</div>
</div>
</div>
</fieldset>
)
export default RadioFieldset;

View file

@ -0,0 +1,55 @@
import React from "react";
import {Switch} from "@headlessui/react";
import {Field} from "react-final-form";
import {classNames} from "../../styles/utils";
interface Props {
name: string;
label: string;
description?: string;
className?: string;
}
const SwitchGroup: React.FC<Props> = ({name, label, description}) => (
<ul className="mt-2 divide-y divide-gray-200">
<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>
{label}
</Switch.Label>
{description && (
<Switch.Description className="text-sm text-gray-500">
{description}
</Switch.Description>
)}
</div>
<Field
name={name}
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'
)}
>
<span className="sr-only">Use setting</span>
<span
aria-hidden="true"
className={classNames(
value ? 'translate-x-5' : 'translate-x-0',
'inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200'
)}
/>
</Switch>
)}
/>
</Switch.Group>
</ul>
)
export default SwitchGroup;

View file

@ -0,0 +1,40 @@
import {Field} from "react-final-form";
import React from "react";
import Error from "./Error";
import {classNames} from "../../styles/utils";
interface Props {
name: string;
label?: string;
placeholder?: string;
className?: string;
required?: boolean;
}
const TextAreaWide: React.FC<Props> = ({name, label, placeholder, required, className}) => (
<div
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} {required && <span className="text-gray-500">*</span>}
</label>
</div>
<div className="sm:col-span-2">
<Field
name={name}
render={({input, meta}) => (
<textarea
{...input}
id={name}
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")}
placeholder={placeholder}
/>
)}
/>
<Error name={name} classNames="block text-red-500 mt-2"/>
</div>
</div>
)
export default TextAreaWide;

View file

@ -0,0 +1,45 @@
import { Field } from "react-final-form";
import React from "react";
import Error from "./Error";
import {classNames} from "../../styles/utils";
type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
interface Props {
name: string;
label?: string;
placeholder?: string;
columns?: COL_WIDTHS;
className?: string;
}
const TextField: React.FC<Props> = ({ name, label, placeholder, columns , className}) => (
<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}
</label>
)}
<Field
name={name}
render={({input, meta}) => (
<input
{...input}
id={name}
type="text"
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"
placeholder={placeholder}
/>
)}
/>
<div>
<Error name={name} classNames="text-red mt-2" />
</div>
</div>
)
export default TextField;

View file

@ -0,0 +1,41 @@
import {Field} from "react-final-form";
import React from "react";
import Error from "./Error";
import {classNames} from "../../styles/utils";
interface Props {
name: string;
label?: string;
placeholder?: string;
className?: string;
required?: boolean;
}
const TextFieldWide: React.FC<Props> = ({name, label, placeholder, required, className}) => (
<div
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} {required && <span className="text-gray-500">*</span>}
</label>
</div>
<div className="sm:col-span-2">
<Field
name={name}
render={({input, meta}) => (
<input
{...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")}
placeholder={placeholder}
/>
)}
/>
<Error name={name} classNames="block text-red-500 mt-2"/>
</div>
</div>
)
export default TextFieldWide;

View file

@ -0,0 +1,6 @@
export { default as TextField } from "./TextField";
export { default as TextFieldWide } from "./TextFieldWide";
export { default as TextAreaWide } from "./TextAreaWide";
export { default as MultiSelectField } from "./MultiSelectField";
export { default as RadioFieldset } from "./RadioFieldset";
export { default as SwitchGroup } from "./SwitchGroup";