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

[dome] form warnings

parent 5764a486
No related branches found
No related tags found
No related merge requests found
...@@ -9,11 +9,12 @@ ...@@ -9,11 +9,12 @@
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import React from 'react'; import React from 'react';
import * as Utils from 'dome/utils'; import { SVG } from 'dome/controls/icons';
import * as Utils from 'dome/misc/utils';
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) => true | 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;
...@@ -24,15 +25,23 @@ export type Callback<A> = (value: A, valid: boolean) => void; ...@@ -24,15 +25,23 @@ export type Callback<A> = (value: A, valid: boolean) => void;
export function inRange( export function inRange(
a: number, a: number,
b: number, b: number,
msg?: string,
): Checker<number> { ): Checker<number> {
return (v: number) => (a <= v && v <= b) || msg || 'Invalid Range'; return (v: number) => (a <= v && v <= b);
} }
export function validate<A>(value: A, checker?: Checker<A>): Error { export function validate<A>(
value: A,
checker: undefined | Checker<A>,
message: undefined | string,
): Error {
if (checker) { if (checker) {
const r = checker(value); try {
return r === true ? undefined : r; const r = checker(value);
if (r === undefined || r === true) return undefined;
return message || r || 'Invalid Field';
} catch (err) {
return err.toString();
}
} }
return undefined; return undefined;
} }
...@@ -44,7 +53,7 @@ export function useCallback<A>( ...@@ -44,7 +53,7 @@ export function useCallback<A>(
error: Error, error: Error,
onChange?: Callback<A>, onChange?: Callback<A>,
) { ) {
React.useMemo( React.useEffect(
() => { if (onChange) onChange(value, isValid(error)); }, () => { if (onChange) onChange(value, isValid(error)); },
[value, error, onChange], [value, error, onChange],
); );
...@@ -130,6 +139,7 @@ export function Filter(props: FilterProps & Children) { ...@@ -130,6 +139,7 @@ export function Filter(props: FilterProps & Children) {
export interface FieldProps<A> { export interface FieldProps<A> {
state: State<A>; state: State<A>;
checker?: Checker<A>; checker?: Checker<A>;
message?: string;
onChange?: Callback<A>; onChange?: Callback<A>;
latency?: number; latency?: number;
} }
...@@ -137,21 +147,21 @@ export interface FieldProps<A> { ...@@ -137,21 +147,21 @@ export interface FieldProps<A> {
type FilterState<A> = [A, Setter<A>, Error]; type FilterState<A> = [A, Setter<A>, Error];
export function useField<A>(props: FieldProps<A>): FilterState<A> { export function useField<A>(props: FieldProps<A>): FilterState<A> {
const { checker, latency = 0, onChange } = props; const { checker, message, 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); const newError = validate(newValue, checker, message);
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); const newError = validate(newValue, checker, message);
setValue(newValue); setValue(newValue);
setError(newError); setError(newError);
if (onChange) onChange(newValue, isValid(newError)); if (onChange) onChange(newValue, isValid(newError));
...@@ -160,7 +170,7 @@ export function useField<A>(props: FieldProps<A>): FilterState<A> { ...@@ -160,7 +170,7 @@ export function useField<A>(props: FieldProps<A>): FilterState<A> {
setCurrent(newValue); setCurrent(newValue);
propagate(newValue); propagate(newValue);
} }
}, [checker, latency, onChange, setValue, setError]); }, [checker, message, latency, onChange, setValue, setError]);
return [current, update, error]; return [current, update, error];
}; };
...@@ -188,4 +198,35 @@ export const Form = (props: FormProps) => { ...@@ -188,4 +198,35 @@ export const Form = (props: FormProps) => {
); );
} }
export interface WarningProps {
/** Short warn message in case of error. */
warn?: string;
/** Error description (in tooltip if warn, on hover otherwized). */
error?: Error;
/** Label offset. */
offset?: number;
}
/** Warning badge */
export function Warning(props: WarningProps) {
const { error, warn, offset = 0 } = props;
if (!error) return null;
const hovered = warn ? warn : error;
const tooltip = warn ? error : undefined;
const style = warn ? { width: 'max-content' } : undefined;
return (
<div
className="dome-xIcon dome-xForm-error"
style={{ top: offset - 2 }} >
<SVG id="WARNING" size={11} title={tooltip} />
<span
className="dome-xForm-warning"
style={style}>
{hovered}
</span>
</div>
);
};
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
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