adds accordion, details query syntax, various small tweaks

This commit is contained in:
Emma
2023-06-15 05:18:45 -06:00
parent f84ef2ee19
commit 4ca8379bec
13 changed files with 307 additions and 39 deletions

View File

@@ -3,14 +3,16 @@ import { FieldType, FieldTypes, fieldTypeOptions, fieldTypesWithValues } from '.
import { useObjectStateWrapper } from '../../hooks/useObjectState';
import { ValueField } from './value-field';
import { HelpPopper } from '../Poppables/help';
import { Icon } from '../Icon';
interface IProps {
update: (arg: FieldType) => void;
field: FieldType;
fieldName: string;
deleteField: (arg: string) => void;
}
export const FieldEditor: FC<IProps> = ({ update, field, fieldName }) => {
export const FieldEditor: FC<IProps> = ({ update, field, fieldName, deleteField }) => {
const { bindProperty, bindPropertyCheck } = useObjectStateWrapper(field, (e) => update(typeof e === 'function' ? e(field) : e))
const shouldShowValueField = useCallback(() => fieldTypesWithValues.includes(field.type) || field.isConstant, [field.isConstant, field.type]);
@@ -22,7 +24,7 @@ export const FieldEditor: FC<IProps> = ({ update, field, fieldName }) => {
return (
<li className="odd:bg-black/50">
<p>{fieldName}</p>
<div className=" flex gap-x-4 items-center p-2">
<div className=" flex gap-x-4 items-center p-2 w-full">
<label className="w-min">
Field Type:&nbsp;
<select className="capitalize" {...bindProperty('type')}>
@@ -40,10 +42,20 @@ export const FieldEditor: FC<IProps> = ({ update, field, fieldName }) => {
<p className="text-sm">Constant values can't be overwritten in publications. When a dice field is set to a constant value, it instead rolls a dice of that value whenever this field is displayed (unless exported). This could be useful for a randomly generated scenario or for cards being drawn as the dice value will automatically be determined by the dice roll.</p>
</HelpPopper>
</span>
<label className="w-min">
Minimum:
<input className="w-12" type="number" {...bindProperty('minimum')} />
</label>
<label className="w-min">
Limit:
<input className="w-12" type="number" {...bindProperty('limit')} />
</label>
<HelpPopper>
<p className="text-sm">Minimum and Limit apply to the number of entries allowed for this field, not the maximum and minimum value. Set the minimum to 0 to make a field optional. Set the limit to 0 to allow for unlimited entries.</p>
</HelpPopper>
<button className="no-default self-end ml-auto" onClick={() => deleteField(fieldName)}>
<Icon className="svg-red-700 hover:svg-red-500 trash-can w-6 h-6" icon="trash"></Icon>
</button>
</div>
</li>
)

View File

@@ -135,8 +135,7 @@ export const SchemaBuilder: FC = () => {
</div>
</div>
<div className="panel basis-1/3">
<SchemaViewer schema={schema} onTypeClick={selectTypeForEdit} />
<div className="flex gap-2 mt-2">
<div className="flex gap-2 mb-2">
<button
onClick={saveSchema}
disabled={lastSaved === schema}
@@ -151,6 +150,7 @@ export const SchemaBuilder: FC = () => {
Discard Changes
</button>
</div>
<SchemaViewer schema={schema} onTypeClick={selectTypeForEdit} />
</div>
</div>
)

View File

@@ -1,6 +1,7 @@
import { FC, useCallback } from 'react'
import { FieldType, FieldTypes, Schema, TypeType, fieldTypesWithValues } from '../../types/schema'
import { Truncate } from '../Poppables/truncation';
import { Accordion, AccordionContent } from '../../lib/accordion';
interface IProps {
schema: Schema;
@@ -40,25 +41,30 @@ export const SchemaViewer: FC<IProps> = ({ schema, onTypeClick }) => {
</ul>
<hr />
<p className="font-bold italic">Types</p>
<ul className="rounded-lg overflow-hidden">
<ul className="rounded-lg overflow-hidden grid">
{Object.entries(schema.types).map(([typeKey, type]) => (
<li
key={'type viewer' + typeKey}
onClick={() => onTypeClick && onTypeClick(typeKey, type)}
// onClick={() => onTypeClick && onTypeClick(typeKey, type)}
data-clickable={!!onTypeClick}
className="odd:bg-black/50 p-2 data-[clickable=true]:cursor-pointer"
className="odd:bg-black/50 p-2 group overflow-hidden"
>
<p className="mb-2 font-bold">{typeKey}</p>
<div className="grid grid-cols-3 gap-2">
{Object.entries(type).map(([fieldKey, field]) => (
<div key={'field viewer' + fieldKey} className="rounded-lg border border-olive-drab p-2">
<p className="font-bold">{fieldKey}</p>
<p className="font-thin capitalize text-xs">{field.type}</p>
<p className="font-thin capitalize text-xs">Maximum entries: {field.limit === 0 ? 'unlimited ' : field.limit}</p>
{(field.isConstant || fieldTypesWithValues.includes(field.type)) && <p className="font-thin capitalize text-xs">{createValueLable(field)} <Truncate>{field.value}</Truncate></p>}
<Accordion
title={<p className="group-data-[expanded]/controlled:mb-2 transition-all font-bold">{typeKey}</p>}
>
<AccordionContent>
<div className="grid grid-cols-2 gap-2">
{Object.entries(type).map(([fieldKey, field]) => (
<div key={'field viewer' + fieldKey} className="rounded-lg border border-olive-drab p-2">
<p className="font-bold">{fieldKey}</p>
<p className="font-thin capitalize text-xs">{field.type}</p>
<p className="font-thin capitalize text-xs">Maximum entries: {field.limit === 0 ? 'unlimited ' : field.limit}</p>
{(field.isConstant || fieldTypesWithValues.includes(field.type)) && <p className="font-thin capitalize text-xs">{createValueLable(field)} <Truncate>{field.value}</Truncate></p>}
</div>
))}
</div>
))}
</div>
</AccordionContent>
</Accordion>
</li>
))}
</ul>

View File

@@ -30,6 +30,7 @@ export const TypeEditor: FCC<IProps> = ({ saveType, name, type: passedType }) =>
value: '',
isConstant: false,
limit: 1,
minimum: 1,
}
});
resetPropertyName();
@@ -43,14 +44,22 @@ export const TypeEditor: FCC<IProps> = ({ saveType, name, type: passedType }) =>
passedType && setType(passedType);
}, [passedType, setType]);
const deleteField = useCallback((name: string) => {
setType(t => {
const fields = {...t};
delete fields[name];
return fields;
})
}, [setType])
return (
<div>
<p className="subheader">{passedType ? 'Editing' : 'Creating'} type "{name}"</p>
<input type="text" {...bindPropertyName} />
<button disabled={!propertyName} onClick={addField}>Add Field</button>
<ul className="rounded-lg overflow-hidden">
{Object.entries(type).filter(([k]) => !constantProperties.includes(k)).map(([key, value]) => (
<FieldEditor field={value} update={updateField(key)} fieldName={key} />
{Object.entries(type).reverse().filter(([k]) => !constantProperties.includes(k)).map(([key, value]) => (
<FieldEditor field={value} update={updateField(key)} fieldName={key} deleteField={deleteField} />
))}
</ul>
<div>

View File

@@ -61,7 +61,7 @@ export const ValueField: FC<IValueProps> = ({ type, bind }) => {
)
case FieldTypes.text:
return (
<label className="w-min">Value:<input type="number" {...bind} /></label>
<label className="w-min">Value:<input type="text" {...bind} /></label>
)
case FieldTypes.select:
return (

View File

@@ -5,12 +5,14 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
name: {
isConstant: false,
limit: 1,
minimum: 1,
type: FieldTypes.text,
value: ''
},
body: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes['long text'],
value: ''
},
@@ -19,6 +21,7 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
steps: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes.type,
value: 'section'
}
@@ -27,12 +30,14 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
name: {
isConstant: false,
limit: 1,
minimum: 1,
type: FieldTypes.text,
value: ''
},
link: {
isConstant: false,
limit: 1,
minimum: 1,
type: FieldTypes.text,
value: ''
},
@@ -41,6 +46,7 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
items: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes['long text'],
value: ''
}
@@ -49,6 +55,7 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
columns: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes.any,
value: ''
}
@@ -57,12 +64,14 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
rows: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes.type,
value: 'tableRow'
},
header: {
isConstant: false,
limit: 1,
minimum: 0,
type: FieldTypes.type,
value: 'tableRow'
}

View File

@@ -42,7 +42,11 @@
}
button:not(.no-default) {
@apply interactive bg-olive-drab p-1
@apply interactive p-1
}
button:not([class*="bg-"]):not(.no-default) {
@apply bg-olive-drab
}
.interactive {
@@ -66,11 +70,14 @@
animation: fade 300ms forwards ease-in reverse;
}
.trash-can, .anvil {
.trash-can,
.anvil {
overflow: visible;
}
.trash-can path.trash-lid, .anvil .anvil-base, .anvil .anvil-body {
.trash-can path.trash-lid,
.anvil .anvil-base,
.anvil .anvil-body {
transition: 300ms transform, 300ms rotate, 300ms fill, 300ms stroke;
}
@@ -82,6 +89,7 @@
.anvil:hover .anvil-base {
transform: translate(0px, 5%);
}
.anvil:hover .anvil-body {
transform: translate(0px, -5%);
}

View File

@@ -0,0 +1,68 @@
import { FC, PropsWithChildren, ReactNode, useCallback, useState } from 'react'
import { FCC } from '../../types';
interface IProps {
expandOnHover?: boolean;
expanded?: boolean;
title?: ReactNode;
}
export const Accordion: FCC<IProps> = ({ children, expandOnHover, expanded, title }) => {
const [open, setOpen] = useState(false);
return (
<div
data-expanded={open || expanded}
data-expandOnHover={expandOnHover}
className={(expandOnHover ? 'group/hover' : 'group/controlled') + ' group'}
onClick={() => !title && !expandOnHover && setOpen(!open)}
>
{!!title && (
<div className="flex justify-between cursor-pointer" onClick={() => !expandOnHover && setOpen(!open)}>
{title}
<div
className={`
group-hover/hover:-rotate-180
group-data-[expanded]:-rotate-180
transition-transform
duration-500
grid
rounded-full
h-min
mr-2
mt-1
scale-y-50
`}
>
<span className="block w-2 h-2 rotate-45 border-r-2 border-b-2 place-self-center"></span>
<span className="block w-2 h-2 rotate-45 border-r-2 border-b-2 place-self-center"></span>
</div>
</div>
)}
{children}
</div>
)
}
export const AccordionContent: FC<PropsWithChildren> = ({ children }) => {
const [height, setHeight] = useState(0);
const updateRef = useCallback((node: HTMLDivElement | null) => {
if (node) {
setHeight(node.clientHeight)
} else {
setHeight(0)
}
}, [])
const Child = () => <div className="absolute bottom-0 w-full" ref={updateRef}>
{children}
</div>
return (
<div className="relative overflow-hidden">
{<Child />}
<span style={{ ['--v-height' as never]: height + 'px' }} className="w-0 block h-0 group-hover/hover:h-variable group-data-[expanded]/controlled:h-variable transition-all duration-700" />
</div>
);
}

View File

@@ -7,6 +7,7 @@ export type FieldType = {
value: string;
isConstant: boolean;
limit: number;
minimum: number;
};
export type TypeType = Record<string, FieldType>;