@dtdot/lego 0.19.3 → 0.19.7

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.
@@ -1,11 +1,14 @@
1
1
  /// <reference types="react" />
2
2
  export declare type ImageUploadMode = 'fill' | 'form';
3
3
  interface ImageUploadProps {
4
- name: string;
5
- value?: string;
6
- onChange?: (value: string) => void;
7
- onSearch?: () => void;
8
- uploadFn?: (file: File) => Promise<string>;
4
+ 'name': string;
5
+ 'value'?: string;
6
+ 'error'?: string;
7
+ 'uploading'?: boolean;
8
+ 'onChange'?: (value: string) => void;
9
+ 'onSearch'?: () => void;
10
+ 'uploadFn'?: (file: File) => Promise<string>;
11
+ 'data-cy'?: string;
9
12
  }
10
- declare const ImageUpload: ({ name, value, onChange, onSearch }: ImageUploadProps) => JSX.Element;
13
+ declare const ImageUpload: ({ name, value, "error": propsError, "uploading": uploadingProp, onChange, onSearch, "data-cy": dataCy, }: ImageUploadProps) => JSX.Element;
11
14
  export default ImageUpload;
@@ -1,10 +1,11 @@
1
- import React, { useContext, useRef } from 'react';
2
- import { faCloudUploadAlt, faSearch } from '@fortawesome/free-solid-svg-icons';
1
+ import React, { useContext, useRef, useState } from 'react';
2
+ import { faCloudUploadAlt, faExclamationCircle, faSearch } from '@fortawesome/free-solid-svg-icons';
3
3
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4
4
  import styled from 'styled-components';
5
5
  import { motion } from 'framer-motion';
6
6
  import FileContext from '../../contexts/File.context';
7
7
  import useFormNode from '../Form/useFormNode.hook';
8
+ import Loader from '../Loader/Loader.component';
8
9
  const UploadContainer = styled.div `
9
10
  position: relative;
10
11
  min-height: 144px;
@@ -24,6 +25,13 @@ const UploadInnerContainer = styled.div `
24
25
  display: flex;
25
26
  justify-content: space-evenly;
26
27
  `;
28
+ const LoaderContainer = styled.div `
29
+ width: 100%;
30
+ height: 100%;
31
+ display: flex;
32
+ justify-content: center;
33
+ align-items: center;
34
+ `;
27
35
  const IconContainer = styled(motion.div) `
28
36
  flex-grow: 1;
29
37
  margin: 8px;
@@ -55,10 +63,42 @@ const Image = styled.img `
55
63
  height: 100%;
56
64
  object-fit: cover;
57
65
  `;
58
- const ImageUpload = ({ name, value, onChange, onSearch }) => {
66
+ const ErrorMessage = styled.div `
67
+ padding-left: 6px;
68
+
69
+ font-family: ${(props) => props.theme.fonts.default.family};
70
+ font-size: ${(props) => props.theme.fonts.default.size};
71
+ color: ${(props) => props.theme.colours.statusDanger.main};
72
+ `;
73
+ const ErrorContainer = styled(motion.div) `
74
+ position: absolute;
75
+ left: 10px;
76
+ top: 10px;
77
+ width: 100%;
78
+ display: flex;
79
+ align-items: center;
80
+
81
+ font-size: ${(props) => props.theme.fonts.default.size};
82
+ color: ${(props) => props.theme.colours.statusDanger.main};
83
+ `;
84
+ const ErrorInner = styled.div `
85
+ width: 24px;
86
+ height: 24px;
87
+
88
+ display: flex;
89
+ justify-content: center;
90
+ align-items: center;
91
+ cursor: pointer;
92
+ `;
93
+ const errorVariants = {
94
+ show: { opacity: 1 },
95
+ };
96
+ const ImageUpload = ({ name, value, 'error': propsError, 'uploading': uploadingProp, onChange, onSearch, 'data-cy': dataCy, }) => {
59
97
  const { upload, getUrl } = useContext(FileContext);
60
- const { value: contextValue, onChange: contextOnChange } = useFormNode(name);
98
+ const [uploading, setUploading] = useState(false);
99
+ const { value: contextValue, error: contextError, onChange: contextOnChange } = useFormNode(name);
61
100
  const inputRef = useRef();
101
+ const error = contextError || propsError;
62
102
  const handleUploadClicked = () => {
63
103
  if (inputRef && inputRef.current) {
64
104
  inputRef.current.click();
@@ -67,18 +107,29 @@ const ImageUpload = ({ name, value, onChange, onSearch }) => {
67
107
  const handleUpload = async (event) => {
68
108
  if (event.target.files && upload) {
69
109
  const file = event.target.files[0];
70
- const url = await upload(file);
71
- if (onChange) {
72
- onChange(url);
110
+ setUploading(true);
111
+ try {
112
+ const url = await upload(file);
113
+ if (onChange) {
114
+ onChange(url);
115
+ }
116
+ if (contextOnChange) {
117
+ contextOnChange(url);
118
+ }
73
119
  }
74
- if (contextOnChange) {
75
- contextOnChange(url);
120
+ finally {
121
+ setUploading(false);
76
122
  }
77
123
  }
78
124
  };
79
125
  const internalValue = value ? value : contextValue;
126
+ if (uploading || uploadingProp) {
127
+ return (React.createElement(UploadContainer, { "data-cy": dataCy },
128
+ React.createElement(LoaderContainer, null,
129
+ React.createElement(Loader, null))));
130
+ }
80
131
  if (!internalValue) {
81
- return (React.createElement(UploadContainer, null,
132
+ return (React.createElement(UploadContainer, { "data-cy": dataCy },
82
133
  React.createElement(UploadInnerContainer, null,
83
134
  React.createElement(IconContainer, { whileHover: { scale: 1.05 }, onClick: handleUploadClicked, "data-cy": 'button-image-upload' },
84
135
  React.createElement(FontAwesomeIcon, { icon: faCloudUploadAlt })),
@@ -86,8 +137,13 @@ const ImageUpload = ({ name, value, onChange, onSearch }) => {
86
137
  React.createElement(UploadVerticalDivider, null),
87
138
  React.createElement(IconContainer, { whileHover: { scale: 1.05 }, onClick: onSearch, "data-cy": 'button-image-search' },
88
139
  React.createElement(FontAwesomeIcon, { icon: faSearch }))))),
89
- React.createElement(HiddenInput, { value: '', ref: inputRef, type: 'file', onChange: handleUpload, "data-cy": 'input-image-hidden' })));
140
+ React.createElement(HiddenInput, { value: '', ref: inputRef, type: 'file', onChange: handleUpload, "data-cy": 'input-image-hidden' }),
141
+ error && (React.createElement(ErrorContainer, { animate: error ? 'show' : undefined, style: { opacity: 0 }, variants: errorVariants, transition: { type: 'spring', duration: 0.3 }, "data-cy": 'error-indicator' },
142
+ React.createElement(ErrorInner, null,
143
+ React.createElement(FontAwesomeIcon, { icon: faExclamationCircle })),
144
+ React.createElement(ErrorMessage, { "data-cy": 'error-message' }, error)))));
90
145
  }
91
- return React.createElement(Image, { src: getUrl(internalValue) });
146
+ return (React.createElement("div", { "data-cy": dataCy },
147
+ React.createElement(Image, { "data-cy": 'uploaded-image', src: getUrl(internalValue) })));
92
148
  };
93
149
  export default ImageUpload;
@@ -7,7 +7,7 @@ import styled, { css } from 'styled-components';
7
7
  import getThemeControlColours from '../../theme/helpers/getThemeControlColours';
8
8
  import useFormNode, { getValue } from '../Form/useFormNode.hook';
9
9
  export const INPUT_HEIGHT = 48;
10
- const InputContainer = styled(motion.div) `
10
+ const InputContainer = styled.div `
11
11
  position: relative;
12
12
  margin: 2px 0;
13
13
 
@@ -117,13 +117,14 @@ const Input = React.forwardRef(function ForwardRefInput(props, ref) {
117
117
  onBlur();
118
118
  }
119
119
  };
120
+ const animationVariant = error ? (isFocused ? 'errorFocus' : 'error') : undefined;
120
121
  return (React.createElement("div", null,
121
122
  label && React.createElement(InputLabel, { htmlFor: name }, label),
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: getValue(value, contextValue), onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, autoFocus: autoFocus, "data-cy": dataCy || 'input' }),
124
- React.createElement(ErrorContainer, { animate: error ? 'show' : undefined, style: { opacity: 0 }, variants: errorVariants, transition: { type: 'spring', duration: 0.3 } },
123
+ React.createElement(InputContainer, { "data-cy": dataCy },
124
+ React.createElement(StyledInput, { ref: ref, animate: animationVariant, variants: inputVariants, transition: { type: 'spring', duration: 0.3 }, type: type, name: name, placeholder: placeholder, value: getValue(value, contextValue), onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, autoFocus: autoFocus, "data-cy": 'input' }),
125
+ React.createElement(ErrorContainer, { animate: error ? 'show' : undefined, style: { opacity: 0 }, variants: errorVariants, transition: { type: 'spring', duration: 0.3 }, "data-cy": 'error-indicator' },
125
126
  React.createElement(ErrorInner, null,
126
127
  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
+ error && (React.createElement(ErrorMessage, { style: { opacity: 0, y: 0 }, animate: animationVariant, variants: messageVariants, transition: { type: 'spring', duration: 0.3 }, "data-cy": 'error-message' }, error)))));
128
129
  });
129
130
  export default Input;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtdot/lego",
3
- "version": "0.19.3",
3
+ "version": "0.19.7",
4
4
  "description": "Some reusable components for building my applications",
5
5
  "main": "build/index.js",
6
6
  "scripts": {
@@ -44,7 +44,7 @@
44
44
  "prettier-eslint": "^13.0.0",
45
45
  "react": "^17.0.2",
46
46
  "react-dom": "^17.0.2",
47
- "storybook-addon-styled-component-theme": "^1.3.0",
47
+ "storybook-addon-styled-component-theme": "^2.0.0",
48
48
  "styled-components": "^5.3.5",
49
49
  "typescript": "^4.5.5"
50
50
  },