ui(admin): add egg variable elements
This commit is contained in:
parent
e2de673488
commit
7239f0e336
|
@ -39,7 +39,7 @@ const EggRouter = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
clearFlashes('egg');
|
clearFlashes('egg');
|
||||||
|
|
||||||
getEgg(Number(match.params?.id))
|
getEgg(Number(match.params?.id), [ 'variables' ])
|
||||||
.then(egg => setEgg(egg))
|
.then(egg => setEgg(egg))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
|
@ -1,11 +1,96 @@
|
||||||
|
import { Form, Formik, useFormikContext } from 'formik';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
import { object } from 'yup';
|
||||||
|
import { Egg, EggVariable } from '@/api/admin/eggs/getEgg';
|
||||||
import AdminBox from '@/components/admin/AdminBox';
|
import AdminBox from '@/components/admin/AdminBox';
|
||||||
import { Egg } from '@/api/admin/eggs/getEgg';
|
import Checkbox from '@/components/elements/Checkbox';
|
||||||
|
import Field, { FieldRow, TextareaField } from '@/components/elements/Field';
|
||||||
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
|
|
||||||
|
function EggVariableForm ({ variable: { name } }: { variable: EggVariable }) {
|
||||||
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
export default ({ egg }: { egg: Egg }) => {
|
|
||||||
return (
|
return (
|
||||||
<AdminBox title={'Variables'}>
|
<AdminBox css={tw`relative w-full`} title={<p css={tw`text-sm uppercase`}>{name}</p>}>
|
||||||
{egg.name}
|
<SpinnerOverlay visible={isSubmitting}/>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
id={'name'}
|
||||||
|
name={'name'}
|
||||||
|
label={'Name'}
|
||||||
|
type={'text'}
|
||||||
|
css={tw`mb-6`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextareaField
|
||||||
|
id={'description'}
|
||||||
|
name={'description'}
|
||||||
|
label={'Description'}
|
||||||
|
rows={3}
|
||||||
|
css={tw`mb-4`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FieldRow>
|
||||||
|
<Field
|
||||||
|
id={'envVariable'}
|
||||||
|
name={'envVariable'}
|
||||||
|
label={'Environment Variable'}
|
||||||
|
type={'text'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
id={'defaultValue'}
|
||||||
|
name={'defaultValue'}
|
||||||
|
label={'Default Value'}
|
||||||
|
type={'text'}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
|
||||||
|
<div css={tw`flex flex-row mb-6`}>
|
||||||
|
<Checkbox
|
||||||
|
id={'userViewable'}
|
||||||
|
name={'userViewable'}
|
||||||
|
label={'User Viewable'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
id={'userEditable'}
|
||||||
|
name={'userEditable'}
|
||||||
|
label={'User Editable'}
|
||||||
|
css={tw`ml-auto`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
id={'rules'}
|
||||||
|
name={'rules'}
|
||||||
|
label={'Validation Rules'}
|
||||||
|
type={'text'}
|
||||||
|
css={tw`mb-2`}
|
||||||
|
/>
|
||||||
</AdminBox>
|
</AdminBox>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export default function EggVariablesContainer ({ egg }: { egg: Egg }) {
|
||||||
|
const submit = () => {
|
||||||
|
//
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
onSubmit={submit}
|
||||||
|
initialValues={{
|
||||||
|
}}
|
||||||
|
validationSchema={object().shape({
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<div css={tw`grid grid-cols-1 lg:grid-cols-2 gap-x-8 gap-y-6`}>
|
||||||
|
{egg.relations?.variables?.map(v => <EggVariableForm key={v.id} variable={v}/>)}
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,42 +1,35 @@
|
||||||
|
import Label from '@/components/elements/Label';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Field, FieldProps } from 'formik';
|
import { Field, FieldProps } from 'formik';
|
||||||
import Input from '@/components/elements/Input';
|
import Input from '@/components/elements/Input';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
label?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type OmitFields = 'ref' | 'name' | 'value' | 'type' | 'checked' | 'onClick' | 'onChange';
|
type OmitFields = 'ref' | 'name' | 'value' | 'type';
|
||||||
|
|
||||||
type InputProps = Omit<JSX.IntrinsicElements['input'], OmitFields>;
|
type InputProps = Omit<JSX.IntrinsicElements['input'], OmitFields>;
|
||||||
|
|
||||||
const Checkbox = ({ name, value, className, ...props }: Props & InputProps) => (
|
const Checkbox = ({ name, label, className, ...props }: Props & InputProps) => (
|
||||||
<Field name={name}>
|
<Field name={name}>
|
||||||
{({ field, form }: FieldProps) => {
|
{({ field }: FieldProps) => {
|
||||||
if (!Array.isArray(field.value)) {
|
|
||||||
console.error('Attempting to mount a checkbox using a field value that is not an array.');
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<div css={tw`flex flex-row`} className={className}>
|
||||||
{...field}
|
<Input
|
||||||
{...props}
|
{...field}
|
||||||
className={className}
|
{...props}
|
||||||
type={'checkbox'}
|
css={tw`w-5 h-5 mr-2`}
|
||||||
checked={(field.value || []).includes(value)}
|
type={'checkbox'}
|
||||||
onClick={() => form.setFieldTouched(field.name, true)}
|
/>
|
||||||
onChange={e => {
|
{label &&
|
||||||
const set = new Set(field.value);
|
<div css={tw`flex-1`}>
|
||||||
set.has(value) ? set.delete(value) : set.add(value);
|
<Label noBottomSpacing>{label}</Label>
|
||||||
|
</div>}
|
||||||
field.onChange(e);
|
</div>
|
||||||
form.setFieldValue(field.name, Array.from(set));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { forwardRef } from 'react';
|
|
||||||
import { Field as FormikField, FieldProps } from 'formik';
|
import { Field as FormikField, FieldProps } from 'formik';
|
||||||
|
import React, { forwardRef } from 'react';
|
||||||
|
import tw, { styled } from 'twin.macro';
|
||||||
import Input, { Textarea } from '@/components/elements/Input';
|
import Input, { Textarea } from '@/components/elements/Input';
|
||||||
import Label from '@/components/elements/Label';
|
|
||||||
import InputError from '@/components/elements/InputError';
|
import InputError from '@/components/elements/InputError';
|
||||||
import tw from 'twin.macro';
|
import Label from '@/components/elements/Label';
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -11,15 +11,17 @@ interface OwnProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
validate?: (value: any) => undefined | string | Promise<any>;
|
validate?: (value: any) => undefined | string | Promise<any>;
|
||||||
|
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = OwnProps & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name'>;
|
type Props = OwnProps & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name'>;
|
||||||
|
|
||||||
const Field = forwardRef<HTMLInputElement, Props>(({ id, name, light = false, label, description, validate, ...props }, ref) => (
|
const Field = forwardRef<HTMLInputElement, Props>(({ id, name, light = false, label, description, validate, className, ...props }, ref) => (
|
||||||
<FormikField innerRef={ref} name={name} validate={validate}>
|
<FormikField innerRef={ref} name={name} validate={validate}>
|
||||||
{
|
{
|
||||||
({ field, form: { errors, touched } }: FieldProps) => (
|
({ field, form: { errors, touched } }: FieldProps) => (
|
||||||
<div>
|
<div className={className}>
|
||||||
{label &&
|
{label &&
|
||||||
<div css={tw`flex flex-row`} title={description}>
|
<div css={tw`flex flex-row`} title={description}>
|
||||||
<Label htmlFor={id} isLight={light}>{label}</Label>
|
<Label htmlFor={id} isLight={light}>{label}</Label>
|
||||||
|
@ -43,15 +45,17 @@ const Field = forwardRef<HTMLInputElement, Props>(({ id, name, light = false, la
|
||||||
));
|
));
|
||||||
Field.displayName = 'Field';
|
Field.displayName = 'Field';
|
||||||
|
|
||||||
type Props2 = OwnProps & Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'name'>;
|
export default Field;
|
||||||
|
|
||||||
export const TextareaField = forwardRef<HTMLTextAreaElement, Props2>(
|
type TextareaProps = OwnProps & Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'name'>;
|
||||||
function TextareaField ({ id, name, light = false, label, description, validate, ...props }, ref) {
|
|
||||||
|
export const TextareaField = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||||
|
function TextareaField ({ id, name, light = false, label, description, validate, className, ...props }, ref) {
|
||||||
return (
|
return (
|
||||||
<FormikField innerRef={ref} name={name} validate={validate}>
|
<FormikField innerRef={ref} name={name} validate={validate}>
|
||||||
{
|
{
|
||||||
({ field, form: { errors, touched } }: FieldProps) => (
|
({ field, form: { errors, touched } }: FieldProps) => (
|
||||||
<div>
|
<div className={className}>
|
||||||
{label && <Label htmlFor={id} isLight={light}>{label}</Label>}
|
{label && <Label htmlFor={id} isLight={light}>{label}</Label>}
|
||||||
<Textarea
|
<Textarea
|
||||||
id={id}
|
id={id}
|
||||||
|
@ -72,4 +76,22 @@ export const TextareaField = forwardRef<HTMLTextAreaElement, Props2>(
|
||||||
);
|
);
|
||||||
TextareaField.displayName = 'TextareaField';
|
TextareaField.displayName = 'TextareaField';
|
||||||
|
|
||||||
export default Field;
|
export const FieldRow = styled.div`
|
||||||
|
${tw`mb-6 md:w-full md:flex md:flex-row`};
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
${tw`md:w-full md:flex md:flex-col mb-6 md:mb-0`};
|
||||||
|
|
||||||
|
&:first-of-type {
|
||||||
|
${tw`md:mr-3`};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:first-of-type):not(:last-of-type) {
|
||||||
|
${tw`md:mx-3`};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
${tw`md:ml-3`};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import tw, { styled } from 'twin.macro';
|
import tw, { styled } from 'twin.macro';
|
||||||
|
|
||||||
const Label = styled.label<{ isLight?: boolean }>`
|
const Label = styled.label<{ isLight?: boolean, noBottomSpacing?: boolean }>`
|
||||||
${tw`block text-xs uppercase text-neutral-200 mb-1 sm:mb-2`};
|
${tw`block text-xs uppercase text-neutral-200`};
|
||||||
|
${props => !props.noBottomSpacing && tw`mb-1 sm:mb-2`};
|
||||||
${props => props.isLight && tw`text-neutral-700`};
|
${props => props.isLight && tw`text-neutral-700`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,48 @@
|
||||||
import tw, { styled } from 'twin.macro';
|
import tw, { styled } from 'twin.macro';
|
||||||
import Checkbox from '@/components/elements/Checkbox';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useStoreState } from 'easy-peasy';
|
import { useStoreState } from 'easy-peasy';
|
||||||
import Label from '@/components/elements/Label';
|
import Label from '@/components/elements/Label';
|
||||||
|
import { Field, FieldProps } from 'formik';
|
||||||
|
import Input from '@/components/elements/Input';
|
||||||
|
|
||||||
|
interface CheckboxProps {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckboxOmitFields = 'ref' | 'name' | 'value' | 'type' | 'checked' | 'onClick' | 'onChange';
|
||||||
|
|
||||||
|
type InputProps = Omit<JSX.IntrinsicElements['input'], CheckboxOmitFields> & CheckboxProps;
|
||||||
|
|
||||||
|
const Checkbox = ({ name, value, className, ...props }: InputProps) => (
|
||||||
|
<Field name={name}>
|
||||||
|
{({ field, form }: FieldProps) => {
|
||||||
|
if (!Array.isArray(field.value)) {
|
||||||
|
console.error('Attempting to mount a checkbox using a field value that is not an array.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
{...props}
|
||||||
|
className={className}
|
||||||
|
type={'checkbox'}
|
||||||
|
checked={(field.value || []).includes(value)}
|
||||||
|
onClick={() => form.setFieldTouched(field.name, true)}
|
||||||
|
onChange={e => {
|
||||||
|
const set = new Set(field.value);
|
||||||
|
set.has(value) ? set.delete(value) : set.add(value);
|
||||||
|
|
||||||
|
field.onChange(e);
|
||||||
|
form.setFieldValue(field.name, Array.from(set));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
|
||||||
const Container = styled.label`
|
const Container = styled.label`
|
||||||
${tw`flex items-center border border-transparent rounded md:p-2 transition-colors duration-75`};
|
${tw`flex items-center border border-transparent rounded md:p-2 transition-colors duration-75`};
|
||||||
|
|
Loading…
Reference in New Issue