adds accordion, details query syntax, various small tweaks
This commit is contained in:
@@ -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:
|
||||
<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>
|
||||
)
|
||||
|
@@ -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>
|
||||
)
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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 (
|
||||
|
@@ -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'
|
||||
}
|
||||
|
@@ -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%);
|
||||
}
|
||||
|
68
project-warstone/src/lib/accordion/index.tsx
Normal file
68
project-warstone/src/lib/accordion/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
@@ -7,6 +7,7 @@ export type FieldType = {
|
||||
value: string;
|
||||
isConstant: boolean;
|
||||
limit: number;
|
||||
minimum: number;
|
||||
};
|
||||
|
||||
export type TypeType = Record<string, FieldType>;
|
||||
|
Reference in New Issue
Block a user