Skip to content
Snippets Groups Projects
Commit 441cbf58 authored by Loïc Correnson's avatar Loïc Correnson
Browse files

[dome] form sections

parent 078eecad
No related branches found
No related tags found
No related merge requests found
...@@ -9,13 +9,20 @@ ...@@ -9,13 +9,20 @@
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import React from 'react'; import React from 'react';
import { SVG } from 'dome/controls/icons'; import * as Dome from 'dome';
import * as Utils from 'dome/misc/utils'; import * as Utils from 'dome/misc/utils';
import { SVG } from 'dome/controls/icons';
import {
MonitorAll,
useMonitor,
useIfMonitor,
useMonitoredItem,
} from 'dome/data/monitors';
export type Error = undefined | string; export type Error = undefined | string;
export type Setter<A> = (value: A) => void; export type Setter<A> = (value: A) => void;
export type Checker<A> = (value: A) => boolean | Error; export type Checker<A> = (value: A) => boolean | Error;
export type State<A> = [A, Setter<A>] export type State<A> = [A, Setter<A>];
export type Callback<A> = (value: A, valid: boolean) => void; export type Callback<A> = (value: A, valid: boolean) => void;
export type FieldState<A> = [A, Setter<A>, Error]; export type FieldState<A> = [A, Setter<A>, Error];
...@@ -33,13 +40,13 @@ export function inRange( ...@@ -33,13 +40,13 @@ export function inRange(
export function validate<A>( export function validate<A>(
value: A, value: A,
checker: undefined | Checker<A>, checker: undefined | Checker<A>,
message: undefined | string, onError: undefined | string,
): Error { ): Error {
if (checker) { if (checker) {
try { try {
const r = checker(value); const r = checker(value);
if (r === undefined || r === true) return undefined; if (r === undefined || r === true) return undefined;
return message || r || 'Invalid Field'; return onError || r || 'Invalid Field';
} catch (err) { } catch (err) {
return err.toString(); return err.toString();
} }
...@@ -100,7 +107,7 @@ export interface FilterProps { ...@@ -100,7 +107,7 @@ export interface FilterProps {
disabled?: boolean; disabled?: boolean;
} }
export interface Children { children: React.ReactNode; } export interface Children { children: React.ReactNode }
/* --------------------------------------------------------------------------*/ /* --------------------------------------------------------------------------*/
/* --- Form Context ---*/ /* --- Form Context ---*/
...@@ -109,6 +116,7 @@ export interface Children { children: React.ReactNode; } ...@@ -109,6 +116,7 @@ export interface Children { children: React.ReactNode; }
interface FormContext { interface FormContext {
disabled: boolean; disabled: boolean;
hidden: boolean; hidden: boolean;
monitor?: MonitorAll;
} }
const CONTEXT = React.createContext<FormContext | undefined>(undefined); const CONTEXT = React.createContext<FormContext | undefined>(undefined);
...@@ -119,12 +127,13 @@ const HIDDEN = ...@@ -119,12 +127,13 @@ const HIDDEN =
const DISABLED = const DISABLED =
({ disabled = false, enabled = true }: FilterProps) => disabled || !enabled; ({ disabled = false, enabled = true }: FilterProps) => disabled || !enabled;
function useContext(props: FilterProps): FormContext { function useContext(props?: FilterProps): FormContext {
const Parent = React.useContext(CONTEXT); const Parent = React.useContext(CONTEXT);
return { return {
hidden: HIDDEN(props) || (Parent?.hidden ?? false), hidden: (props && HIDDEN(props)) || (Parent?.hidden ?? false),
disabled: DISABLED(props) || (Parent?.disabled ?? false), disabled: (props && DISABLED(props)) || (Parent?.disabled ?? false),
} monitor: Parent?.monitor,
};
} }
/** @category Form Containers */ /** @category Form Containers */
...@@ -138,11 +147,16 @@ export function Filter(props: FilterProps & Children) { ...@@ -138,11 +147,16 @@ export function Filter(props: FilterProps & Children) {
); );
} }
/** @category Form Containers */
export function useValidity() {
const { monitor } = useContext();
return useIfMonitor(monitor) ?? true;
}
/* --------------------------------------------------------------------------*/ /* --------------------------------------------------------------------------*/
/* --- Main Form Container ---*/ /* --- Main Form Container ---*/
/* --------------------------------------------------------------------------*/ /* --------------------------------------------------------------------------*/
/** @category Form Containers */ /** @category Form Containers */
export interface FormProps extends FilterProps, Children { export interface FormProps extends FilterProps, Children {
/** Additional container class. */ /** Additional container class. */
...@@ -165,7 +179,7 @@ export const Form = (props: FormProps) => { ...@@ -165,7 +179,7 @@ export const Form = (props: FormProps) => {
</Filter> </Filter>
</div> </div>
); );
} };
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// --- Warning Badge // --- Warning Badge
...@@ -184,28 +198,78 @@ export interface WarningProps { ...@@ -184,28 +198,78 @@ export interface WarningProps {
export function Warning(props: WarningProps) { export function Warning(props: WarningProps) {
const { error, warn, offset = 0 } = props; const { error, warn, offset = 0 } = props;
if (!error) return null; if (!error) return null;
const hovered = warn ? warn : error; const hovered = warn || error;
const tooltip = warn ? error : undefined; const tooltip = warn ? error : undefined;
const style = warn ? { width: 'max-content' } : undefined; const style = warn ? { width: 'max-content' } : undefined;
return ( return (
<div <div
className="dome-xIcon dome-xForm-error" className="dome-xIcon dome-xForm-error"
style={{ top: offset - 2 }} > style={{ top: offset - 2 }}
>
<SVG id="WARNING" size={11} title={tooltip} /> <SVG id="WARNING" size={11} title={tooltip} />
<span <span
className="dome-xForm-warning" className="dome-xForm-warning"
style={style}> style={style}
>
{hovered} {hovered}
</span> </span>
</div> </div>
); );
}; }
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// --- Section Container // --- Section Container
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
export interface SectionProps extends FilterProps, Children {
/** Section name. */
label: string;
/** Tooltip text. */
title?: string;
/** Warning Error. */
onError?: string;
/** Fold/Unfold settings. */
settings?: string;
/** Fold/Unfold state (defaults to false). */
unfold?: boolean;
}
/** Form Section. */
export function Section(props: SectionProps) {
const { label, title, children, onError, ...filter } = props;
const { disabled, hidden, monitor } = useContext(filter);
const local = React.useMemo(() => new MonitorAll(), []);
const valid = useMonitor(local);
useMonitoredItem(monitor, valid);
const [unfold, flip] = Dome.useFlipSettings(props.settings, props.unfold);
if (hidden) return null;
const hasWarning = unfold && !disabled && !valid;
const cssTitle = Utils.classes(
'dome-text-title',
disabled && 'dome-disabled',
);
return (
<CONTEXT.Provider value={{ hidden, disabled, monitor: local }}>
<div className="dome-xForm-section">
<div className="dome-xForm-fold" onClick={flip}>
<SVG id={unfold ? 'TRIANGLE.DOWN' : 'TRIANGLE.RIGHT'} size={11} />
</div>
<label className={cssTitle} title={title}>
{label}
</label>
{hasWarning && <Warning warn={onError} />}
</div>
{unfold && children}
{unfold && <div className="dome-xForm-hsep" />}
</CONTEXT.Provider>
);
}
/* --------------------------------------------------------------------------*/ /* --------------------------------------------------------------------------*/
/* --- Value Filter --- */ /* --- Value Filter --- */
/* --------------------------------------------------------------------------*/ /* --------------------------------------------------------------------------*/
...@@ -214,28 +278,28 @@ export function Warning(props: WarningProps) { ...@@ -214,28 +278,28 @@ export function Warning(props: WarningProps) {
export interface FieldProps<A> { export interface FieldProps<A> {
state: State<A>; state: State<A>;
checker?: Checker<A>; checker?: Checker<A>;
message?: string; onError?: string;
onChange?: Callback<A>; onChange?: Callback<A>;
latency?: number; latency?: number;
} }
/** @category Form Fields */ /** @category Form Fields */
export function useField<A>(props: FieldProps<A>): FieldState<A> { export function useField<A>(props: FieldProps<A>): FieldState<A> {
const { checker, message, latency = 0, onChange } = props; const { checker, onError, latency = 0, onChange } = props;
const [value, setValue] = props.state; const [value, setValue] = props.state;
const [current, setCurrent] = React.useState<A>(value); const [current, setCurrent] = React.useState<A>(value);
const [error, setError] = React.useState<Error>(undefined); const [error, setError] = React.useState<Error>(undefined);
const update = React.useMemo(() => { const update = React.useMemo(() => {
if (!latency) if (!latency)
return (newValue: A) => { return (newValue: A) => {
const newError = validate(newValue, checker, message); const newError = validate(newValue, checker, onError);
setCurrent(newValue); setCurrent(newValue);
setValue(newValue); setValue(newValue);
setError(newError); setError(newError);
if (onChange) onChange(newValue, isValid(newError)); if (onChange) onChange(newValue, isValid(newError));
}; };
const propagate = debounce((newValue) => { const propagate = debounce((newValue) => {
const newError = validate(newValue, checker, message); const newError = validate(newValue, checker, onError);
setValue(newValue); setValue(newValue);
setError(newError); setError(newError);
if (onChange) onChange(newValue, isValid(newError)); if (onChange) onChange(newValue, isValid(newError));
...@@ -243,9 +307,9 @@ export function useField<A>(props: FieldProps<A>): FieldState<A> { ...@@ -243,9 +307,9 @@ export function useField<A>(props: FieldProps<A>): FieldState<A> {
return (newValue: A) => { return (newValue: A) => {
setCurrent(newValue); setCurrent(newValue);
propagate(newValue); propagate(newValue);
} };
}, [checker, message, latency, onChange, setValue, setError]); }, [checker, onError, latency, onChange, setValue, setError]);
return [current, update, error]; return [current, update, error];
}; }
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment