@dtdot/lego 2.0.0-13 → 2.0.0-14

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,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { SelectOption } from '../common/Options.component';
2
3
  export interface IInputProps {
3
4
  'name'?: string;
4
5
  'label'?: string;
@@ -13,6 +14,7 @@ export interface IInputProps {
13
14
  'onFocus'?: () => void;
14
15
  'onBlur'?: () => void;
15
16
  'data-cy'?: string;
17
+ 'suggestions'?: SelectOption[];
16
18
  }
17
19
  declare const Input: React.ForwardRefExoticComponent<IInputProps & React.RefAttributes<HTMLInputElement>>;
18
20
  export default Input;
@@ -8,6 +8,7 @@ import ControlDescription from '../../shared/ControlDescription';
8
8
  import ControlLabel from '../../shared/ControlLabel';
9
9
  import { ControlStyles } from '../../shared/ControlStyles';
10
10
  import useFormNode, { getValue } from '../Form/useFormNode.hook';
11
+ import { OptionsPopper } from '../common/Options.component';
11
12
  const InputContainer = styled.div `
12
13
  position: relative;
13
14
  border-radius: 2px;
@@ -62,11 +63,22 @@ const messageVariants = {
62
63
  errorFocus: { opacity: 1, y: -4 },
63
64
  };
64
65
  const Input = React.forwardRef(function ForwardRefInput(props, ref) {
65
- const { label, name, description, placeholder, disabled, type = 'text', autoFocus, value, 'error': propsError, onChange, onFocus, onBlur, 'data-cy': dataCy, } = props;
66
+ const { label, name, description, placeholder, disabled, type = 'text', autoFocus, value, 'error': propsError, onChange, onFocus, onBlur, 'data-cy': dataCy, suggestions, } = props;
67
+ const [isOpen, setIsOpen] = useState(false);
66
68
  const [isFocused, setIsFocused] = useState(false);
69
+ const [referenceElement, setReferenceElement] = useState();
67
70
  const { value: contextValue, error: contextError, onChange: contextOnChange } = useFormNode(name);
68
71
  const error = contextError || propsError;
69
72
  const splitDescription = description ? description.split('\\n').map((str) => str.trim()) : undefined;
73
+ const handleSetSuggestion = (value) => {
74
+ setIsOpen(false);
75
+ if (onChange) {
76
+ onChange(value);
77
+ }
78
+ if (contextOnChange) {
79
+ contextOnChange(value);
80
+ }
81
+ };
70
82
  const handleChange = (e) => {
71
83
  if (onChange) {
72
84
  onChange(e.target.value);
@@ -87,17 +99,22 @@ const Input = React.forwardRef(function ForwardRefInput(props, ref) {
87
99
  onBlur();
88
100
  }
89
101
  };
102
+ const handleClick = () => {
103
+ setIsOpen(true);
104
+ };
90
105
  const animationVariant = error ? (isFocused ? 'errorFocus' : 'error') : undefined;
106
+ const filteredOptions = suggestions?.filter((option) => !value || option.label.toLowerCase().includes(value.toLowerCase()));
91
107
  return (React.createElement("div", null,
92
108
  label && React.createElement(ControlLabel, { htmlFor: name }, label),
93
- React.createElement(InputContainer, { "data-cy": dataCy },
94
- React.createElement(StyledInput, { ref: ref, animate: animationVariant, variants: inputVariants, transition: { type: 'spring', duration: 0.3 }, type: type, name: name, placeholder: placeholder, disabled: disabled, value: getValue(value, contextValue), onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, autoFocus: autoFocus, "data-cy": 'input' }),
109
+ React.createElement(InputContainer, { "data-cy": dataCy, ref: setReferenceElement },
110
+ React.createElement(StyledInput, { ref: ref, animate: animationVariant, variants: inputVariants, transition: { type: 'spring', duration: 0.3 }, type: type, name: name, placeholder: placeholder, disabled: disabled, value: getValue(value, contextValue), onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, onClick: handleClick, autoFocus: autoFocus, "data-cy": 'input' }),
95
111
  React.createElement(ErrorContainer, { animate: error ? 'show' : undefined, style: { opacity: 0 }, variants: errorVariants, transition: { type: 'spring', duration: 0.3 }, "data-cy": 'error-indicator' },
96
112
  React.createElement(ErrorInner, null,
97
113
  React.createElement(FontAwesomeIcon, { icon: faExclamationCircle }))),
98
114
  error && (React.createElement(ErrorMessage, { style: { opacity: 0, y: 0 }, animate: animationVariant, variants: messageVariants, transition: { type: 'spring', duration: 0.3 }, "data-cy": 'error-message' }, error))),
99
115
  splitDescription && (React.createElement(ControlDescription, null, splitDescription.map((line, index) => (React.createElement(React.Fragment, null,
100
116
  index !== 0 && React.createElement("br", null),
101
- line)))))));
117
+ line))))),
118
+ filteredOptions && isOpen && (React.createElement(OptionsPopper, { referenceElement: referenceElement, options: filteredOptions, onSelect: handleSetSuggestion, onClose: () => setIsOpen(false) }))));
102
119
  });
103
120
  export default Input;
@@ -1,8 +1,5 @@
1
1
  /// <reference types="react" />
2
- export type SelectOption = {
3
- value: string;
4
- label: string;
5
- };
2
+ import { SelectOption } from '../common/Options.component';
6
3
  export interface ISelectProps {
7
4
  'name'?: string;
8
5
  'label'?: string;
@@ -1,15 +1,13 @@
1
1
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
2
- import React, { useCallback, useEffect, useState } from 'react';
3
- import ReactDOM from 'react-dom';
4
- import { usePopper } from 'react-popper';
2
+ import React, { Fragment, useState } from 'react';
5
3
  import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
6
- import { motion } from 'framer-motion';
7
- import styled, { useTheme } from 'styled-components';
4
+ import styled from 'styled-components';
8
5
  import ControlDescription from '../../shared/ControlDescription';
9
6
  import ControlLabel from '../../shared/ControlLabel';
10
7
  import { ControlStyles } from '../../shared/ControlStyles';
11
8
  import getThemeControlColours from '../../theme/helpers/getThemeControlColours';
12
9
  import useFormNode, { getValue } from '../Form/useFormNode.hook';
10
+ import { OptionsPopper } from '../common/Options.component';
13
11
  const ControlOuter = styled.div `
14
12
  position: relative;
15
13
  `;
@@ -37,54 +35,23 @@ const PlaceholderText = styled.div `
37
35
  const ValueText = styled.div `
38
36
  color: ${(props) => getThemeControlColours(props.theme).font};
39
37
  `;
40
- const OptionsContainer = styled.div `
41
- width: 100%;
42
- /* position: absolute; */
43
- background-color: ${(props) => props.theme.colours.controlBackground};
44
- z-index: 10000;
45
-
46
- box-shadow: ${(props) => props.theme.shadows.small};
47
- `;
48
- const Option = styled(motion.div) `
49
- color: ${(props) => getThemeControlColours(props.theme).font};
50
- background-color: ${(props) => props.theme.colours.controlBackgroundDisabled};
51
- height: 36px;
52
- display: flex;
53
- align-items: center;
54
- padding: 0 12px;
55
- cursor: pointer;
56
- `;
57
38
  const Select = (props) => {
58
- const theme = useTheme();
59
39
  const [isOpen, setIsOpen] = useState(false);
60
40
  const [referenceElement, setReferenceElement] = useState();
61
- const [popperElement, setPopperElement] = useState();
62
41
  const { label, name, description, placeholder, 'value': propsValue, 'data-cy': dataCy, options } = props;
63
42
  const { value: contextValue, onChange: contextOnChange } = useFormNode(name);
64
- const { styles, attributes } = usePopper(referenceElement, popperElement, { placement: 'bottom-start' });
65
43
  const value = getValue(propsValue, contextValue);
66
44
  const splitDescription = description ? description.split('\\n').map((str) => str.trim()) : undefined;
67
- const selectValue = (option) => {
45
+ const handleSelect = (value) => {
68
46
  setIsOpen(false);
69
47
  if (contextOnChange) {
70
- contextOnChange(option.value);
48
+ contextOnChange(value);
71
49
  }
72
50
  if (props.onChange) {
73
- props.onChange(option.value);
51
+ props.onChange(value);
74
52
  }
75
53
  };
76
54
  const valueLabel = value && options.find((o) => o.value === value)?.label;
77
- const handleGlobalClick = useCallback((event) => {
78
- if (!popperElement?.contains(event.target)) {
79
- setIsOpen(false);
80
- }
81
- }, [setIsOpen, popperElement]);
82
- useEffect(() => {
83
- document.addEventListener('mouseup', handleGlobalClick);
84
- return () => {
85
- document.removeEventListener('mouseup', handleGlobalClick);
86
- };
87
- }, [handleGlobalClick, popperElement]);
88
55
  return (React.createElement("div", null,
89
56
  label && React.createElement(ControlLabel, { htmlFor: name }, label),
90
57
  React.createElement(ControlOuter, { ref: setReferenceElement },
@@ -94,10 +61,8 @@ const Select = (props) => {
94
61
  value && React.createElement(ValueText, null, valueLabel)),
95
62
  React.createElement(IconContainer, null,
96
63
  React.createElement(FontAwesomeIcon, { icon: isOpen ? faChevronUp : faChevronDown }))),
97
- isOpen &&
98
- ReactDOM.createPortal(React.createElement("div", { ref: setPopperElement, style: { ...styles.popper, zIndex: 999, width: referenceElement?.offsetWidth }, ...attributes.popper },
99
- React.createElement(OptionsContainer, null, options.map((option) => (React.createElement(Option, { whileHover: { backgroundColor: theme.colours.controlBorder }, transition: { type: 'spring', duration: 0.2 }, key: option.value, onClick: () => selectValue(option) }, option.label))))), document.querySelector('body'))),
100
- splitDescription && (React.createElement(ControlDescription, null, splitDescription.map((line, index) => (React.createElement(React.Fragment, null,
64
+ isOpen && (React.createElement(OptionsPopper, { referenceElement: referenceElement, options: options, onSelect: handleSelect, onClose: () => setIsOpen(false) }))),
65
+ splitDescription && (React.createElement(ControlDescription, null, splitDescription.map((line, index) => (React.createElement(Fragment, { key: index },
101
66
  index !== 0 && React.createElement("br", null),
102
67
  line)))))));
103
68
  };
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ export declare const OptionsContainer: import("styled-components").StyledComponent<"div", import("styled-components").DefaultTheme, {}, never>;
3
+ export declare const Option: import("styled-components").StyledComponent<import("framer-motion").ForwardRefComponent<HTMLDivElement, import("framer-motion").HTMLMotionProps<"div">>, import("styled-components").DefaultTheme, {}, never>;
4
+ export type SelectOption = {
5
+ value: string;
6
+ label: string;
7
+ };
8
+ export interface SelectOptionsProps {
9
+ options: SelectOption[];
10
+ onSelect: (value: string) => void;
11
+ }
12
+ export declare const SelectOptions: ({ options, onSelect }: SelectOptionsProps) => JSX.Element;
13
+ export interface SelectOptionsPopperProps {
14
+ referenceElement: any;
15
+ options: SelectOption[];
16
+ onSelect: (value: string) => void;
17
+ onClose: () => void;
18
+ }
19
+ export declare const OptionsPopper: ({ referenceElement, options, onSelect, onClose }: SelectOptionsPopperProps) => React.ReactPortal;
@@ -0,0 +1,43 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import { usePopper } from 'react-popper';
4
+ import { motion } from 'framer-motion';
5
+ import styled, { useTheme } from 'styled-components';
6
+ import getThemeControlColours from '../../theme/helpers/getThemeControlColours';
7
+ export const OptionsContainer = styled.div `
8
+ width: 100%;
9
+ background-color: ${(props) => props.theme.colours.controlBackground};
10
+ z-index: 10000;
11
+
12
+ box-shadow: ${(props) => props.theme.shadows.small};
13
+ `;
14
+ export const Option = styled(motion.div) `
15
+ color: ${(props) => getThemeControlColours(props.theme).font};
16
+ background-color: ${(props) => props.theme.colours.controlBackgroundDisabled};
17
+ height: 36px;
18
+ display: flex;
19
+ align-items: center;
20
+ padding: 0 12px;
21
+ cursor: pointer;
22
+ `;
23
+ export const SelectOptions = ({ options, onSelect }) => {
24
+ const theme = useTheme();
25
+ return (React.createElement(OptionsContainer, null, options.map((option) => (React.createElement(Option, { whileHover: { backgroundColor: theme.colours.controlBorder }, transition: { type: 'spring', duration: 0.2 }, key: option.value, onClick: () => onSelect(option.value) }, option.label)))));
26
+ };
27
+ export const OptionsPopper = ({ referenceElement, options, onSelect, onClose }) => {
28
+ const [popperElement, setPopperElement] = useState();
29
+ const { styles, attributes } = usePopper(referenceElement, popperElement, { placement: 'bottom-start' });
30
+ const handleGlobalClick = useCallback((event) => {
31
+ if (!popperElement?.contains(event.target)) {
32
+ onClose();
33
+ }
34
+ }, [onClose, popperElement]);
35
+ useEffect(() => {
36
+ document.addEventListener('mouseup', handleGlobalClick);
37
+ return () => {
38
+ document.removeEventListener('mouseup', handleGlobalClick);
39
+ };
40
+ }, [handleGlobalClick, popperElement]);
41
+ return ReactDOM.createPortal(React.createElement("div", { ref: setPopperElement, style: { ...styles.popper, zIndex: 999, width: referenceElement?.offsetWidth }, ...attributes.popper },
42
+ React.createElement(SelectOptions, { options: options, onSelect: onSelect })), document.querySelector('body'));
43
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtdot/lego",
3
- "version": "2.0.0-13",
3
+ "version": "2.0.0-14",
4
4
  "description": "Some reusable components for building my applications",
5
5
  "main": "build/index.js",
6
6
  "scripts": {