@dtdot/lego 0.17.0 → 0.17.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/Form/Form.component.d.ts +3 -2
- package/build/components/Form/Form.component.js +2 -1
- package/build/components/Form/FormState.context.d.ts +1 -0
- package/build/components/Form/FormState.context.js +1 -0
- package/build/components/Form/useFormNode.hook.d.ts +7 -2
- package/build/components/Form/useFormNode.hook.js +10 -1
- package/build/components/ImageUpload/ImageUpload.component.js +1 -1
- package/build/components/Input/Input.component.d.ts +6 -4
- package/build/components/Input/Input.component.js +80 -8
- package/build/components/Input/Input.stories.d.ts +1 -0
- package/build/components/Input/Input.stories.js +18 -2
- package/build/components/LiveList/LiveList.component.js +32 -16
- package/build/components/LiveList/LiveList.stories.d.ts +2 -0
- package/build/components/LiveList/LiveList.stories.js +32 -2
- package/build/components/LiveList/_LiveListRow.d.ts +2 -1
- package/build/components/LiveList/_LiveListRow.js +8 -6
- package/package.json +1 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
interface FormProps {
|
|
3
|
-
value: any
|
|
3
|
+
value: Record<string, any>;
|
|
4
|
+
errors?: Record<string, any>;
|
|
4
5
|
onChange: (value: any) => void;
|
|
5
6
|
onSubmit?: () => void;
|
|
6
7
|
children: React.ReactNode;
|
|
7
8
|
}
|
|
8
|
-
declare const Form: ({ value, onChange, onSubmit, children }: FormProps) => JSX.Element;
|
|
9
|
+
declare const Form: ({ value, errors, onChange, onSubmit, children }: FormProps) => JSX.Element;
|
|
9
10
|
export default Form;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import FormStateContext from './FormState.context';
|
|
4
|
-
const Form = ({ value, onChange, onSubmit, children }) => {
|
|
4
|
+
const Form = ({ value, errors = {}, onChange, onSubmit, children }) => {
|
|
5
5
|
const onChangeFn = (key, fieldValue) => {
|
|
6
6
|
onChange({
|
|
7
7
|
...value,
|
|
@@ -16,6 +16,7 @@ const Form = ({ value, onChange, onSubmit, children }) => {
|
|
|
16
16
|
};
|
|
17
17
|
const contextValue = {
|
|
18
18
|
value,
|
|
19
|
+
errors,
|
|
19
20
|
onChange: onChangeFn,
|
|
20
21
|
};
|
|
21
22
|
return (React.createElement(FormStateContext.Provider, { value: contextValue },
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
declare function useFormNode<T>(key
|
|
2
|
-
value:
|
|
1
|
+
declare function useFormNode<T = string, K = string>(key?: string): {
|
|
2
|
+
value: undefined;
|
|
3
|
+
error: undefined;
|
|
4
|
+
onChange: undefined;
|
|
5
|
+
} | {
|
|
6
|
+
value: T | undefined;
|
|
7
|
+
error: K | undefined;
|
|
3
8
|
onChange: (_value: T) => void;
|
|
4
9
|
};
|
|
5
10
|
export default useFormNode;
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import { useContext } from 'react';
|
|
2
2
|
import FormStateContext from './FormState.context';
|
|
3
3
|
function useFormNode(key) {
|
|
4
|
-
const { value, onChange } = useContext(FormStateContext);
|
|
4
|
+
const { value, errors, onChange } = useContext(FormStateContext);
|
|
5
|
+
if (!key) {
|
|
6
|
+
return {
|
|
7
|
+
value: undefined,
|
|
8
|
+
error: undefined,
|
|
9
|
+
onChange: undefined,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
5
12
|
const internalOnChange = (_value) => {
|
|
6
13
|
if (onChange) {
|
|
7
14
|
onChange(key, _value);
|
|
8
15
|
}
|
|
9
16
|
};
|
|
10
17
|
const internalValue = value[key];
|
|
18
|
+
const internalError = errors[key];
|
|
11
19
|
return {
|
|
12
20
|
value: internalValue,
|
|
21
|
+
error: internalError,
|
|
13
22
|
onChange: internalOnChange,
|
|
14
23
|
};
|
|
15
24
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export declare const INPUT_HEIGHT = 48;
|
|
3
|
+
export declare const InputStyles: import("styled-components").FlattenInterpolation<import("styled-components").ThemeProps<import("styled-components").DefaultTheme>>;
|
|
3
4
|
export interface IInputProps {
|
|
4
|
-
name
|
|
5
|
+
name?: string;
|
|
5
6
|
label?: string;
|
|
6
7
|
placeholder?: string;
|
|
7
8
|
type?: string;
|
|
8
9
|
value?: string;
|
|
10
|
+
error?: string;
|
|
9
11
|
onChange?: (value: any) => void;
|
|
10
12
|
onFocus?: () => void;
|
|
11
13
|
onBlur?: () => void;
|
|
12
14
|
}
|
|
13
|
-
declare const Input:
|
|
15
|
+
declare const Input: React.ForwardRefExoticComponent<IInputProps & React.RefAttributes<HTMLInputElement>>;
|
|
14
16
|
export default Input;
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import { faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
|
|
3
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
4
|
+
import { motion } from 'framer-motion';
|
|
5
|
+
import React, { useState } from 'react';
|
|
6
|
+
import styled, { css } from 'styled-components';
|
|
4
7
|
import getThemeControlColours from '../../theme/helpers/getThemeControlColours';
|
|
5
8
|
import useFormNode from '../Form/useFormNode.hook';
|
|
9
|
+
export const INPUT_HEIGHT = 48;
|
|
10
|
+
const InputContainer = styled(motion.div) `
|
|
11
|
+
position: relative;
|
|
12
|
+
margin: 2px 0;
|
|
13
|
+
|
|
14
|
+
background-color: ${(props) => props.theme.colours.controlBackground};
|
|
15
|
+
`;
|
|
6
16
|
const InputLabel = styled.label `
|
|
7
17
|
display: block;
|
|
8
18
|
padding-bottom: 8px;
|
|
@@ -11,13 +21,14 @@ const InputLabel = styled.label `
|
|
|
11
21
|
font-family: ${(props) => props.theme.fonts.default.family};
|
|
12
22
|
font-size: ${(props) => props.theme.fonts.default.size};
|
|
13
23
|
`;
|
|
14
|
-
export const
|
|
24
|
+
export const InputStyles = css `
|
|
15
25
|
outline: none;
|
|
16
26
|
box-shadow: none;
|
|
17
27
|
|
|
18
28
|
width: 100%;
|
|
19
|
-
height:
|
|
29
|
+
height: ${INPUT_HEIGHT}px;
|
|
20
30
|
padding: 0 12px;
|
|
31
|
+
scroll-margin-bottom: 100px;
|
|
21
32
|
|
|
22
33
|
font-family: ${(props) => props.theme.fonts.default.family};
|
|
23
34
|
font-size: ${(props) => props.theme.fonts.default.size};
|
|
@@ -40,8 +51,52 @@ export const StyledInput = styled.input `
|
|
|
40
51
|
color: ${(props) => getThemeControlColours(props.theme).placeholder};
|
|
41
52
|
}
|
|
42
53
|
`;
|
|
43
|
-
const
|
|
44
|
-
|
|
54
|
+
const StyledInput = styled(motion.input) `
|
|
55
|
+
${InputStyles}
|
|
56
|
+
`;
|
|
57
|
+
const ErrorMessage = styled(motion.div) `
|
|
58
|
+
position: absolute;
|
|
59
|
+
left: 38px;
|
|
60
|
+
bottom: 0;
|
|
61
|
+
|
|
62
|
+
font-family: ${(props) => props.theme.fonts.default.family};
|
|
63
|
+
font-size: ${(props) => props.theme.fonts.default.size};
|
|
64
|
+
color: ${(props) => props.theme.colours.statusDanger.main};
|
|
65
|
+
`;
|
|
66
|
+
const ErrorContainer = styled(motion.div) `
|
|
67
|
+
position: absolute;
|
|
68
|
+
left: 0;
|
|
69
|
+
top: 0;
|
|
70
|
+
height: 100%;
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
|
|
74
|
+
color: ${(props) => props.theme.colours.statusDanger.main};
|
|
75
|
+
`;
|
|
76
|
+
const ErrorInner = styled.div `
|
|
77
|
+
width: 24px;
|
|
78
|
+
height: 24px;
|
|
79
|
+
|
|
80
|
+
display: flex;
|
|
81
|
+
justify-content: center;
|
|
82
|
+
align-items: center;
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
`;
|
|
85
|
+
const errorVariants = {
|
|
86
|
+
show: { opacity: 1, x: 10 },
|
|
87
|
+
};
|
|
88
|
+
const inputVariants = {
|
|
89
|
+
error: { paddingLeft: '38px' },
|
|
90
|
+
errorFocus: { paddingLeft: '38px', paddingBottom: '18px' },
|
|
91
|
+
};
|
|
92
|
+
const messageVariants = {
|
|
93
|
+
errorFocus: { opacity: 1, y: -4 },
|
|
94
|
+
};
|
|
95
|
+
const Input = React.forwardRef(function ForwardRefInput(props, ref) {
|
|
96
|
+
const { label, name, placeholder, type = 'text', value, error: propsError, onChange, onFocus, onBlur } = props;
|
|
97
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
98
|
+
const { value: contextValue, error: contextError, onChange: contextOnChange } = useFormNode(name);
|
|
99
|
+
const error = contextError || propsError;
|
|
45
100
|
const handleChange = (e) => {
|
|
46
101
|
if (onChange) {
|
|
47
102
|
onChange(e.target.value);
|
|
@@ -50,8 +105,25 @@ const Input = ({ label, name, placeholder, type = 'text', value, onChange, onFoc
|
|
|
50
105
|
contextOnChange(e.target.value);
|
|
51
106
|
}
|
|
52
107
|
};
|
|
108
|
+
const handleFocus = () => {
|
|
109
|
+
setIsFocused(true);
|
|
110
|
+
if (onFocus) {
|
|
111
|
+
onFocus();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const handleBlur = () => {
|
|
115
|
+
setIsFocused(false);
|
|
116
|
+
if (onBlur) {
|
|
117
|
+
onBlur();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
53
120
|
return (React.createElement("div", null,
|
|
54
121
|
label && React.createElement(InputLabel, { htmlFor: name }, label),
|
|
55
|
-
React.createElement(
|
|
56
|
-
}
|
|
122
|
+
React.createElement(InputContainer, { animate: error ? (isFocused ? 'errorFocus' : 'error') : undefined },
|
|
123
|
+
React.createElement(StyledInput, { ref: ref, variants: inputVariants, transition: { type: 'spring', duration: 0.3 }, type: type, name: name, placeholder: placeholder, value: value || contextValue, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur }),
|
|
124
|
+
React.createElement(ErrorContainer, { animate: error ? 'show' : undefined, style: { opacity: 0 }, variants: errorVariants, transition: { type: 'spring', duration: 0.3 } },
|
|
125
|
+
React.createElement(ErrorInner, null,
|
|
126
|
+
React.createElement(FontAwesomeIcon, { icon: faExclamationCircle }))),
|
|
127
|
+
error && (React.createElement(ErrorMessage, { style: { opacity: 0, y: 0 }, variants: messageVariants, transition: { type: 'spring', duration: 0.3 } }, error)))));
|
|
128
|
+
});
|
|
57
129
|
export default Input;
|
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
import { Meta } from '@storybook/react/types-6-0';
|
|
3
3
|
export declare const Standard: () => JSX.Element;
|
|
4
4
|
export declare const WithoutLabels: () => JSX.Element;
|
|
5
|
+
export declare const WithError: () => JSX.Element;
|
|
5
6
|
declare const _default: Meta<import("@storybook/react/types-6-0").Args>;
|
|
6
7
|
export default _default;
|
|
@@ -1,11 +1,27 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Input, ControlGroup } from '../..';
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Button, ButtonGroup, Input, ControlGroup, Spacer } from '../..';
|
|
3
3
|
export const Standard = () => (React.createElement(ControlGroup, null,
|
|
4
4
|
React.createElement(Input, { name: 'one', label: 'A standard input' }),
|
|
5
5
|
React.createElement(Input, { name: 'two', label: 'Another input' })));
|
|
6
6
|
export const WithoutLabels = () => (React.createElement(ControlGroup, null,
|
|
7
7
|
React.createElement(Input, { name: 'one', placeholder: 'A standard input' }),
|
|
8
8
|
React.createElement(Input, { name: 'two', placeholder: 'Another input' })));
|
|
9
|
+
export const WithError = () => {
|
|
10
|
+
const [error, setError] = useState('Input has an error!');
|
|
11
|
+
const clear = () => {
|
|
12
|
+
setError(undefined);
|
|
13
|
+
};
|
|
14
|
+
const validate = () => {
|
|
15
|
+
setError('Input has an error!');
|
|
16
|
+
};
|
|
17
|
+
return (React.createElement(React.Fragment, null,
|
|
18
|
+
React.createElement(ControlGroup, null,
|
|
19
|
+
React.createElement(Input, { name: 'one', error: error, placeholder: 'A standard input' })),
|
|
20
|
+
React.createElement(Spacer, { size: '2x' }),
|
|
21
|
+
React.createElement(ButtonGroup, null,
|
|
22
|
+
React.createElement(Button, { onClick: clear }, "Clear"),
|
|
23
|
+
React.createElement(Button, { onClick: validate }, "Set Errors"))));
|
|
24
|
+
};
|
|
9
25
|
export default {
|
|
10
26
|
title: 'Components/Input',
|
|
11
27
|
component: Input,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { faPlusCircle } from '@fortawesome/free-solid-svg-icons';
|
|
2
2
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
3
|
-
import
|
|
3
|
+
import { motion } from 'framer-motion';
|
|
4
|
+
import React, { useCallback, useContext, useEffect } from 'react';
|
|
4
5
|
import styled from 'styled-components';
|
|
5
6
|
import { v4 as uuidv4 } from 'uuid';
|
|
6
7
|
import useKeypress from '../../hooks/useKeyPress';
|
|
@@ -14,11 +15,12 @@ const AddRow = styled.div `
|
|
|
14
15
|
|
|
15
16
|
margin-top: 4px;
|
|
16
17
|
`;
|
|
17
|
-
const AddRowInner = styled.div `
|
|
18
|
+
const AddRowInner = styled(motion.div) `
|
|
18
19
|
padding: 4px;
|
|
19
20
|
display: flex;
|
|
20
21
|
align-items: center;
|
|
21
22
|
cursor: pointer;
|
|
23
|
+
overflow: hidden;
|
|
22
24
|
|
|
23
25
|
color: ${(props) => props.theme.colours.defaultFont};
|
|
24
26
|
font-family: ${(props) => props.theme.fonts.default.family};
|
|
@@ -27,23 +29,34 @@ const AddRowInner = styled.div `
|
|
|
27
29
|
const IconContainer = styled.div `
|
|
28
30
|
padding-right: 4px;
|
|
29
31
|
`;
|
|
30
|
-
const
|
|
32
|
+
const addVariants = {
|
|
33
|
+
hover: { scale: 1.05 },
|
|
34
|
+
};
|
|
35
|
+
const defaultValue = [{ id: uuidv4(), value: '' }];
|
|
36
|
+
const LiveList = ({ value: inputValue, name, onChange: propsOnChange }) => {
|
|
31
37
|
const { getFocused, requestFocus } = useContext(FocusContext);
|
|
32
|
-
const { value: contextValue, onChange: contextOnChange } = useFormNode(name);
|
|
33
|
-
const value = contextValue || inputValue;
|
|
34
|
-
const
|
|
38
|
+
const { value: contextValue, error: contextError, onChange: contextOnChange } = useFormNode(name);
|
|
39
|
+
const value = contextValue || inputValue || defaultValue;
|
|
40
|
+
const wrappedOnChange = useCallback((val) => {
|
|
41
|
+
if (propsOnChange) {
|
|
42
|
+
propsOnChange(val);
|
|
43
|
+
}
|
|
44
|
+
if (contextOnChange) {
|
|
45
|
+
contextOnChange(val);
|
|
46
|
+
}
|
|
47
|
+
}, [propsOnChange, contextOnChange]);
|
|
35
48
|
useEffect(() => {
|
|
36
49
|
if (!(value === null || value === void 0 ? void 0 : value.length)) {
|
|
37
|
-
|
|
50
|
+
wrappedOnChange([{ id: uuidv4(), value: '' }]);
|
|
38
51
|
}
|
|
39
|
-
}, [value,
|
|
52
|
+
}, [value, wrappedOnChange]);
|
|
40
53
|
useKeypress('Enter', () => {
|
|
41
54
|
const focusedId = getFocused();
|
|
42
55
|
const focusedIndex = value.findIndex((val) => val.id === focusedId);
|
|
43
56
|
const newItem = { id: uuidv4(), value: '' };
|
|
44
57
|
const newValue = [...value];
|
|
45
58
|
newValue.splice(focusedIndex + 1, 0, newItem);
|
|
46
|
-
|
|
59
|
+
wrappedOnChange(newValue);
|
|
47
60
|
requestFocus(newItem.id);
|
|
48
61
|
});
|
|
49
62
|
useKeypress('Backspace', () => {
|
|
@@ -56,8 +69,11 @@ const LiveList = ({ value: inputValue, name, onChange: inputOnChange }) => {
|
|
|
56
69
|
return;
|
|
57
70
|
}
|
|
58
71
|
const focusedIndex = value.findIndex((val) => val.id === focusedId);
|
|
72
|
+
if (focusedIndex === 0) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
59
75
|
const prevId = focusedIndex > 0 ? value[focusedIndex - 1].id : undefined;
|
|
60
|
-
|
|
76
|
+
wrappedOnChange(value.filter((val) => val.id !== focusedId));
|
|
61
77
|
if (prevId) {
|
|
62
78
|
// Timeout to prevent deleting the last char of the previous row
|
|
63
79
|
setTimeout(() => {
|
|
@@ -66,28 +82,28 @@ const LiveList = ({ value: inputValue, name, onChange: inputOnChange }) => {
|
|
|
66
82
|
}
|
|
67
83
|
});
|
|
68
84
|
const handleRowChange = (updateId, updateValue) => {
|
|
69
|
-
|
|
85
|
+
wrappedOnChange(value.map((v) => (v.id === updateId ? { ...v, value: updateValue } : v)));
|
|
70
86
|
};
|
|
71
87
|
const handleRowRemove = (id) => {
|
|
72
88
|
if (value.length === 1) {
|
|
73
89
|
const withValueCleared = [...value];
|
|
74
90
|
value[0].value = '';
|
|
75
|
-
|
|
91
|
+
wrappedOnChange(withValueCleared);
|
|
76
92
|
return;
|
|
77
93
|
}
|
|
78
|
-
|
|
94
|
+
wrappedOnChange(value.filter((val) => val.id !== id));
|
|
79
95
|
};
|
|
80
96
|
const handleRowAdd = () => {
|
|
81
97
|
const newItem = { id: uuidv4(), value: '' };
|
|
82
|
-
|
|
98
|
+
wrappedOnChange([...value, newItem]);
|
|
83
99
|
};
|
|
84
100
|
if (!(value === null || value === void 0 ? void 0 : value.length)) {
|
|
85
101
|
return null;
|
|
86
102
|
}
|
|
87
103
|
return (React.createElement("div", null,
|
|
88
|
-
value.map((val) => (React.createElement(LiveListRow, { key: val.id, id: val.id, value: val.value, onChange: (newVal) => handleRowChange(val.id, newVal), onRemove: () => handleRowRemove(val.id) }))),
|
|
104
|
+
value.map((val) => (React.createElement(LiveListRow, { key: val.id, id: val.id, value: val.value, error: contextError ? contextError[val.id] : undefined, onChange: (newVal) => handleRowChange(val.id, newVal), onRemove: () => handleRowRemove(val.id) }))),
|
|
89
105
|
React.createElement(AddRow, null,
|
|
90
|
-
React.createElement(AddRowInner, { onClick: handleRowAdd },
|
|
106
|
+
React.createElement(AddRowInner, { style: { scale: 1 }, whileHover: 'hover', variants: addVariants, onClick: handleRowAdd },
|
|
91
107
|
React.createElement(IconContainer, null,
|
|
92
108
|
React.createElement(FontAwesomeIcon, { icon: faPlusCircle })),
|
|
93
109
|
"add"))));
|
|
@@ -2,5 +2,7 @@
|
|
|
2
2
|
import { Meta } from '@storybook/react/types-6-0';
|
|
3
3
|
export declare const Standard: () => JSX.Element;
|
|
4
4
|
export declare const InForm: () => JSX.Element;
|
|
5
|
+
export declare const WithValidation: () => JSX.Element;
|
|
6
|
+
export declare const EmptyList: () => JSX.Element;
|
|
5
7
|
declare const _default: Meta<import("@storybook/react/types-6-0").Args>;
|
|
6
8
|
export default _default;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
|
-
import { ControlGroup, Form, Heading, ImageUpload, Input, LiveList, Spacer } from '../..';
|
|
2
|
+
import { Button, ButtonGroup, ControlGroup, Form, Heading, ImageUpload, Input, LiveList, Spacer, Text } from '../..';
|
|
3
3
|
const exampleLiveListValue = [
|
|
4
4
|
{ id: '6f974464-d592-4271-b3ae-2821fffce258', value: '500g chicken' },
|
|
5
5
|
{ id: 'a273d33a-2643-4991-b81d-2beff51d42a8', value: '1/2 cup rice' },
|
|
6
6
|
{ id: '2ecef56f-f725-4f8a-ac84-bd3117e7d50b', value: '1 tbs olive oil' },
|
|
7
7
|
];
|
|
8
|
+
const exampleListErrors = {
|
|
9
|
+
'6f974464-d592-4271-b3ae-2821fffce258': 'Ingredient not found',
|
|
10
|
+
'a273d33a-2643-4991-b81d-2beff51d42a8': 'Ingredient not found',
|
|
11
|
+
};
|
|
8
12
|
export const Standard = () => {
|
|
9
13
|
const [value, setValue] = useState(exampleLiveListValue);
|
|
10
14
|
return (React.createElement(React.Fragment, null,
|
|
@@ -19,7 +23,33 @@ export const InForm = () => {
|
|
|
19
23
|
React.createElement(ImageUpload, { name: 'image' }),
|
|
20
24
|
React.createElement(Input, { name: 'name', placeholder: 'Something tasty..' }),
|
|
21
25
|
React.createElement(Heading.FormHeading, null, "Ingredients"),
|
|
22
|
-
React.createElement(LiveList, { name: 'ingredients'
|
|
26
|
+
React.createElement(LiveList, { name: 'ingredients' }))));
|
|
27
|
+
};
|
|
28
|
+
export const WithValidation = () => {
|
|
29
|
+
const value = { ingredients: exampleLiveListValue };
|
|
30
|
+
const [errors, setErrors] = useState({ ingredients: exampleListErrors });
|
|
31
|
+
const validate = () => {
|
|
32
|
+
setErrors({ ingredients: exampleListErrors });
|
|
33
|
+
};
|
|
34
|
+
const clear = () => {
|
|
35
|
+
setErrors({ ingredients: undefined });
|
|
36
|
+
};
|
|
37
|
+
return (React.createElement(React.Fragment, null,
|
|
38
|
+
React.createElement(Form, { value: value, errors: errors, onChange: () => undefined },
|
|
39
|
+
React.createElement(ControlGroup, { variation: 'comfortable' },
|
|
40
|
+
React.createElement(Text, { variant: 'secondary' }, "Max length 20 chars"),
|
|
41
|
+
React.createElement(LiveList, { name: 'ingredients' }))),
|
|
42
|
+
React.createElement(Spacer, { size: '2x' }),
|
|
43
|
+
React.createElement(ButtonGroup, null,
|
|
44
|
+
React.createElement(Button, { onClick: clear }, "Clear"),
|
|
45
|
+
React.createElement(Button, { onClick: validate }, "Set Errors"))));
|
|
46
|
+
};
|
|
47
|
+
export const EmptyList = () => {
|
|
48
|
+
const [value, setValue] = useState({});
|
|
49
|
+
return (React.createElement(React.Fragment, null,
|
|
50
|
+
React.createElement(Heading.FormHeading, null, "Ingredients"),
|
|
51
|
+
React.createElement(Spacer, { size: '1x' }),
|
|
52
|
+
React.createElement(LiveList, { value: value, onChange: setValue })));
|
|
23
53
|
};
|
|
24
54
|
export default {
|
|
25
55
|
title: 'Components/LiveList',
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
interface LiveListRowProps {
|
|
3
3
|
id: string;
|
|
4
4
|
value: string;
|
|
5
|
+
error?: string;
|
|
5
6
|
onChange: (value: string) => void;
|
|
6
7
|
onRemove: () => void;
|
|
7
8
|
}
|
|
8
|
-
declare const LiveListRow: ({ id, value, onChange, onRemove }: LiveListRowProps) => JSX.Element;
|
|
9
|
+
declare const LiveListRow: ({ id, value, error, onChange, onRemove }: LiveListRowProps) => JSX.Element;
|
|
9
10
|
export default LiveListRow;
|
|
@@ -3,11 +3,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
|
3
3
|
import { motion } from 'framer-motion';
|
|
4
4
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
|
5
5
|
import styled from 'styled-components';
|
|
6
|
-
import { StyledInput } from '../Input/Input.component';
|
|
7
6
|
import { FocusContext } from './_FocusContext';
|
|
7
|
+
import Input from '../Input/Input.component';
|
|
8
8
|
const InputContainer = styled(motion.div) `
|
|
9
9
|
position: relative;
|
|
10
|
-
|
|
10
|
+
margin: 2px 0;
|
|
11
|
+
|
|
12
|
+
background-color: ${(props) => props.theme.colours.controlBackground};
|
|
11
13
|
`;
|
|
12
14
|
const RemoveContainer = styled(motion.div) `
|
|
13
15
|
position: absolute;
|
|
@@ -32,7 +34,7 @@ const removeVariants = {
|
|
|
32
34
|
hover: { opacity: 1, x: -10 },
|
|
33
35
|
focus: { opacity: 1, x: -10 },
|
|
34
36
|
};
|
|
35
|
-
const LiveListRow = ({ id, value, onChange, onRemove }) => {
|
|
37
|
+
const LiveListRow = ({ id, value, error, onChange, onRemove }) => {
|
|
36
38
|
const { onFocus, onBlur, registerFocusable, deregisterFocusable } = useContext(FocusContext);
|
|
37
39
|
const inputRef = useRef(null);
|
|
38
40
|
const [isFocused, setIsFocused] = useState(false);
|
|
@@ -47,7 +49,7 @@ const LiveListRow = ({ id, value, onChange, onRemove }) => {
|
|
|
47
49
|
});
|
|
48
50
|
}, [id, registerFocusable, deregisterFocusable]);
|
|
49
51
|
const handleChange = (e) => {
|
|
50
|
-
onChange(e
|
|
52
|
+
onChange(e);
|
|
51
53
|
};
|
|
52
54
|
const handleFocus = () => {
|
|
53
55
|
setIsFocused(true);
|
|
@@ -58,8 +60,8 @@ const LiveListRow = ({ id, value, onChange, onRemove }) => {
|
|
|
58
60
|
onBlur(id);
|
|
59
61
|
};
|
|
60
62
|
return (React.createElement(InputContainer, { whileHover: 'hover' },
|
|
61
|
-
React.createElement(
|
|
62
|
-
React.createElement(RemoveContainer, { animate: isFocused ? 'focus' : undefined, style: { opacity: 0 }, variants: removeVariants, transition: { duration: 0.
|
|
63
|
+
React.createElement(Input, { onFocus: handleFocus, onBlur: handleBlur, ref: inputRef, value: value || '', error: error, onChange: handleChange }),
|
|
64
|
+
React.createElement(RemoveContainer, { animate: isFocused ? 'focus' : undefined, style: { opacity: 0 }, variants: removeVariants, transition: { type: 'spring', duration: 0.3 } },
|
|
63
65
|
React.createElement(RemoveInner, { onClick: onRemove },
|
|
64
66
|
React.createElement(FontAwesomeIcon, { icon: faTimes })))));
|
|
65
67
|
};
|