zavadil-react-common 1.2.58 → 1.2.61

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.
@@ -0,0 +1,101 @@
1
+ import {useCallback, useEffect, useMemo, useState} from "react";
2
+ import {CancellablePromise, EntityBase} from "zavadil-ts-common";
3
+ import TextInputWithReset from "./TextInputWithReset";
4
+ import {Badge, Dropdown, Stack} from "react-bootstrap";
5
+
6
+ export type AutocompleteSelectProps<T extends EntityBase> = {
7
+ selected?: T | null;
8
+ onChange: (e: T | null) => any;
9
+ onSearch: (text: string) => Promise<Array<T>>;
10
+ disabled?: boolean;
11
+ labelGetter?: (item: T) => string;
12
+ }
13
+
14
+ export function AutocompleteSelect<T extends EntityBase>({selected, disabled, labelGetter, onChange, onSearch}: AutocompleteSelectProps<T>) {
15
+ const [searchPromise, setSearchPromise] = useState<CancellablePromise>();
16
+ const [searchText, setSearchText] = useState<string>();
17
+ const [itemSelection, setItemSelection] = useState<Array<T>>();
18
+
19
+ const finalLabelGetter = useMemo(
20
+ () => labelGetter ? labelGetter : (item: T) => `[${item.id}]`,
21
+ [labelGetter]
22
+ );
23
+
24
+ const finalCss = useMemo(
25
+ () => selected ? 'border-primary' : undefined,
26
+ [selected]
27
+ );
28
+
29
+ useEffect(() => {
30
+ setSearchText(selected ? finalLabelGetter(selected) : '');
31
+ }, [selected, finalLabelGetter]);
32
+
33
+ const reset = useCallback(
34
+ () => {
35
+ onChange(null)
36
+ setSearchText('');
37
+ setItemSelection(undefined);
38
+ },
39
+ []
40
+ );
41
+
42
+ const userChangedText = useCallback(
43
+ (s: string) => {
44
+ setSearchText(s);
45
+ if (searchPromise) {
46
+ searchPromise.cancel();
47
+ }
48
+ const cancellable = new CancellablePromise(onSearch(s));
49
+ cancellable.promise.then(setItemSelection);
50
+ setSearchPromise(cancellable);
51
+ },
52
+ [searchPromise]
53
+ );
54
+
55
+ const userLeftSearch = useCallback(
56
+ () => {
57
+ if (searchPromise) {
58
+ searchPromise.cancel();
59
+ setSearchPromise(undefined);
60
+ }
61
+ if (selected) {
62
+ setSearchText(finalLabelGetter(selected));
63
+ } else {
64
+ setSearchText('');
65
+ }
66
+ setItemSelection(undefined);
67
+ },
68
+ [selected, searchPromise, finalLabelGetter]
69
+ );
70
+
71
+ return (
72
+ <Dropdown defaultShow={false} show={itemSelection !== null} onBlur={userLeftSearch}>
73
+ <Stack direction="horizontal" gap={2}>
74
+ <TextInputWithReset
75
+ disabled={disabled}
76
+ value={searchText}
77
+ onChange={userChangedText}
78
+ onReset={reset}
79
+ className={finalCss}
80
+ />
81
+ {
82
+ selected &&
83
+ <Badge>{selected.id}</Badge>
84
+ }
85
+ </Stack>
86
+
87
+ {
88
+ itemSelection &&
89
+ <Dropdown.Menu>
90
+ {
91
+ itemSelection.map(
92
+ (item) => <Dropdown.Item key={item.id} onSelect={() => onChange(item)}>
93
+ {finalLabelGetter(item)}
94
+ </Dropdown.Item>
95
+ )
96
+ }
97
+ </Dropdown.Menu>
98
+ }
99
+ </Dropdown>
100
+ );
101
+ }
@@ -8,12 +8,13 @@ export type LookupSelectProps = {
8
8
  onChange: (n: number | null | undefined) => any;
9
9
  options?: Array<LookupTableEntity> | null;
10
10
  showEmptyOption?: boolean;
11
+ disabled?: boolean;
11
12
  emptyOptionLabel?: string;
12
13
  sort?: boolean;
13
14
  labelGetter?: <T extends LookupTableEntity>(item: T) => string;
14
15
  }
15
16
 
16
- export function LookupSelect({id, sort, labelGetter, onChange, options, showEmptyOption, emptyOptionLabel}: LookupSelectProps) {
17
+ export function LookupSelect({id, sort, disabled, labelGetter, onChange, options, showEmptyOption, emptyOptionLabel}: LookupSelectProps) {
17
18
  const lOptions: GenericSelectOption<number>[] = useMemo(
18
19
  () => {
19
20
  let result: Array<LookupTableEntity> = []
@@ -35,6 +36,7 @@ export function LookupSelect({id, sort, labelGetter, onChange, options, showEmpt
35
36
 
36
37
  return (
37
38
  <NumberSelect
39
+ disabled={disabled}
38
40
  value={id}
39
41
  options={lOptions}
40
42
  onChange={onChange}
@@ -4,7 +4,7 @@ import {GenericSelectProps, StringSelect} from "./StringSelect";
4
4
 
5
5
  export type NumberSelectProps = GenericSelectProps<number>;
6
6
 
7
- export function NumberSelect({value, options, onChange, showEmptyOption, emptyOptionLabel}: NumberSelectProps) {
7
+ export function NumberSelect({value, options, disabled, onChange, showEmptyOption, emptyOptionLabel}: NumberSelectProps) {
8
8
  const nValue = useMemo(
9
9
  () => {
10
10
  if (!value) return '';
@@ -35,6 +35,7 @@ export function NumberSelect({value, options, onChange, showEmptyOption, emptyOp
35
35
 
36
36
  return (
37
37
  <StringSelect
38
+ disabled={disabled}
38
39
  value={nValue}
39
40
  options={nOptions}
40
41
  onChange={nChange}
@@ -11,12 +11,13 @@ export type GenericSelectProps<T> = {
11
11
  options: Array<GenericSelectOption<T>>;
12
12
  onChange: (id: T | null | undefined) => any;
13
13
  showEmptyOption?: boolean;
14
+ disabled?: boolean;
14
15
  emptyOptionLabel?: string;
15
16
  }
16
17
 
17
18
  export type StringSelectProps = GenericSelectProps<string>;
18
19
 
19
- export function StringSelect({value, options, onChange, showEmptyOption, emptyOptionLabel}: StringSelectProps) {
20
+ export function StringSelect({value, options, disabled, onChange, showEmptyOption, emptyOptionLabel}: StringSelectProps) {
20
21
  if (StringUtil.isEmpty(value) && options.length > 0 && showEmptyOption !== true) {
21
22
  onChange(options[0].id);
22
23
  return <span>{value} - selecting default {options[0].id}</span>;
@@ -26,6 +27,7 @@ export function StringSelect({value, options, onChange, showEmptyOption, emptyOp
26
27
  <Form.Select
27
28
  value={value || ''}
28
29
  onChange={(e) => onChange(e.target.value)}
30
+ disabled={disabled}
29
31
  >
30
32
  {
31
33
  (showEmptyOption === true) && (
@@ -7,9 +7,12 @@ export type TextInputWithResetProps = {
7
7
  value?: string | null;
8
8
  onChange: (s: string) => any;
9
9
  onReset?: () => any;
10
+ disabled?: boolean;
11
+ onBlur?: () => any;
12
+ className?: string;
10
13
  };
11
14
 
12
- export function TextInputWithReset({value, onChange, onReset}: TextInputWithResetProps) {
15
+ export function TextInputWithReset({value, onChange, onReset, onBlur, disabled, className}: TextInputWithResetProps) {
13
16
  const actual = useMemo(
14
17
  () => StringUtil.getNonEmpty(value),
15
18
  [value]
@@ -24,11 +27,13 @@ export function TextInputWithReset({value, onChange, onReset}: TextInputWithRese
24
27
  );
25
28
 
26
29
  return (
27
- <InputGroup>
30
+ <InputGroup className={className}>
28
31
  <Form.Control
29
32
  type="text"
33
+ disabled={disabled}
30
34
  value={actual}
31
35
  onChange={(e) => onChange(e.target.value)}
36
+ onBlur={onBlur}
32
37
  />
33
38
  <Button onClick={reset}>
34
39
  <div className="d-flex align-items-center">