ui(admin): add egg variable elements
This commit is contained in:
parent
e2de673488
commit
7239f0e336
6 changed files with 185 additions and 44 deletions
|
@ -39,7 +39,7 @@ const EggRouter = () => {
|
|||
useEffect(() => {
|
||||
clearFlashes('egg');
|
||||
|
||||
getEgg(Number(match.params?.id))
|
||||
getEgg(Number(match.params?.id), [ 'variables' ])
|
||||
.then(egg => setEgg(egg))
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
|
|
|
@ -1,11 +1,96 @@
|
|||
import { Form, Formik, useFormikContext } from 'formik';
|
||||
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 { 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 (
|
||||
<AdminBox title={'Variables'}>
|
||||
{egg.name}
|
||||
<AdminBox css={tw`relative w-full`} title={<p css={tw`text-sm uppercase`}>{name}</p>}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
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 { Field, FieldProps } from 'formik';
|
||||
import Input from '@/components/elements/Input';
|
||||
import tw from 'twin.macro';
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
value: string;
|
||||
label?: 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>;
|
||||
|
||||
const Checkbox = ({ name, value, className, ...props }: Props & InputProps) => (
|
||||
const Checkbox = ({ name, label, className, ...props }: 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;
|
||||
}
|
||||
|
||||
{({ field }: FieldProps) => {
|
||||
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));
|
||||
}}
|
||||
/>
|
||||
<div css={tw`flex flex-row`} className={className}>
|
||||
<Input
|
||||
{...field}
|
||||
{...props}
|
||||
css={tw`w-5 h-5 mr-2`}
|
||||
type={'checkbox'}
|
||||
/>
|
||||
{label &&
|
||||
<div css={tw`flex-1`}>
|
||||
<Label noBottomSpacing>{label}</Label>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Field>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React, { forwardRef } from 'react';
|
||||
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 Label from '@/components/elements/Label';
|
||||
import InputError from '@/components/elements/InputError';
|
||||
import tw from 'twin.macro';
|
||||
import Label from '@/components/elements/Label';
|
||||
|
||||
interface OwnProps {
|
||||
name: string;
|
||||
|
@ -11,15 +11,17 @@ interface OwnProps {
|
|||
label?: string;
|
||||
description?: string;
|
||||
validate?: (value: any) => undefined | string | Promise<any>;
|
||||
|
||||
className?: string;
|
||||
}
|
||||
|
||||
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}>
|
||||
{
|
||||
({ field, form: { errors, touched } }: FieldProps) => (
|
||||
<div>
|
||||
<div className={className}>
|
||||
{label &&
|
||||
<div css={tw`flex flex-row`} title={description}>
|
||||
<Label htmlFor={id} isLight={light}>{label}</Label>
|
||||
|
@ -43,15 +45,17 @@ const Field = forwardRef<HTMLInputElement, Props>(({ id, name, light = false, la
|
|||
));
|
||||
Field.displayName = 'Field';
|
||||
|
||||
type Props2 = OwnProps & Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'name'>;
|
||||
export default Field;
|
||||
|
||||
export const TextareaField = forwardRef<HTMLTextAreaElement, Props2>(
|
||||
function TextareaField ({ id, name, light = false, label, description, validate, ...props }, ref) {
|
||||
type TextareaProps = OwnProps & Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'name'>;
|
||||
|
||||
export const TextareaField = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
function TextareaField ({ id, name, light = false, label, description, validate, className, ...props }, ref) {
|
||||
return (
|
||||
<FormikField innerRef={ref} name={name} validate={validate}>
|
||||
{
|
||||
({ field, form: { errors, touched } }: FieldProps) => (
|
||||
<div>
|
||||
<div className={className}>
|
||||
{label && <Label htmlFor={id} isLight={light}>{label}</Label>}
|
||||
<Textarea
|
||||
id={id}
|
||||
|
@ -72,4 +76,22 @@ export const TextareaField = forwardRef<HTMLTextAreaElement, Props2>(
|
|||
);
|
||||
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';
|
||||
|
||||
const Label = styled.label<{ isLight?: boolean }>`
|
||||
${tw`block text-xs uppercase text-neutral-200 mb-1 sm:mb-2`};
|
||||
const Label = styled.label<{ isLight?: boolean, noBottomSpacing?: boolean }>`
|
||||
${tw`block text-xs uppercase text-neutral-200`};
|
||||
${props => !props.noBottomSpacing && tw`mb-1 sm:mb-2`};
|
||||
${props => props.isLight && tw`text-neutral-700`};
|
||||
`;
|
||||
|
||||
|
|
|
@ -1,8 +1,48 @@
|
|||
import tw, { styled } from 'twin.macro';
|
||||
import Checkbox from '@/components/elements/Checkbox';
|
||||
import React from 'react';
|
||||
import { useStoreState } from 'easy-peasy';
|
||||
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`
|
||||
${tw`flex items-center border border-transparent rounded md:p-2 transition-colors duration-75`};
|
||||
|
|
Loading…
Reference in a new issue