• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

teableio / teable / 8538004962

03 Apr 2024 11:36AM CUT coverage: 18.233% (-3.3%) from 21.535%
8538004962

Pull #528

github

web-flow
Merge c1a248a6f into 45ee7ebb3
Pull Request #528: feat: Kanban view

575 of 1136 branches covered (50.62%)

29 of 2908 new or added lines in 83 files covered. (1.0%)

5 existing lines in 5 files now uncovered.

6439 of 35315 relevant lines covered (18.23%)

3.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/packages/sdk/src/components/editor/select/EditorMain.tsx
1
import { Check, Plus } from '@teable/icons';
×
2
import {
×
3
  Button,
×
4
  Command,
×
5
  CommandEmpty,
×
6
  CommandGroup,
×
7
  CommandInput,
×
8
  CommandItem,
×
9
  CommandList,
×
10
  cn,
×
11
  useCommandState,
×
12
} from '@teable/ui-lib';
×
13
import type { ForwardRefRenderFunction } from 'react';
×
14
import { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react';
×
NEW
15
import { SelectTag } from '../../cell-value/cell-select/SelectTag';
×
16
import type { ICellEditor, IEditorRef } from '../type';
×
17

×
18
export type ISelectValue<T extends boolean> = T extends true ? string[] : string;
×
19

×
20
export interface ISelectEditorMain<T extends boolean> extends ICellEditor<ISelectValue<T>> {
×
21
  options?: {
×
22
    label: string;
×
23
    value: string;
×
24
    color?: string;
×
25
    backgroundColor?: string;
×
26
  }[];
×
27
  isMultiple?: T;
×
28
  style?: React.CSSProperties;
×
29
  className?: string;
×
30
  onOptionAdd?: (name: string) => Promise<void>;
×
31
}
×
32

×
33
const getValue = (value?: string | string[]) => {
×
34
  if (value == null) return [];
×
35
  if (Array.isArray(value)) return value;
×
36
  return [value];
×
37
};
×
38

×
39
const SelectEditorMainBase: ForwardRefRenderFunction<
×
40
  IEditorRef<string | string[] | undefined>,
×
41
  ISelectEditorMain<boolean>
×
42
> = (props, ref) => {
×
43
  const {
×
44
    value: originValue,
×
45
    options = [],
×
46
    isMultiple,
×
47
    style,
×
48
    className,
×
49
    onChange,
×
50
    onOptionAdd,
×
51
  } = props;
×
52

×
53
  const [value, setValue] = useState<string[]>(getValue(originValue));
×
54
  const [searchValue, setSearchValue] = useState('');
×
55
  const inputRef = useRef<HTMLInputElement | null>(null);
×
56

×
57
  useImperativeHandle(ref, () => ({
×
58
    focus: () => {
×
59
      setSearchValue('');
×
60
      inputRef.current?.focus();
×
61
    },
×
62
    setValue: (value?: string | string[]) => {
×
63
      setValue(getValue(value));
×
64
    },
×
65
  }));
×
66

×
67
  const onSelect = (val: string) => {
×
68
    setSearchValue('');
×
69
    if (isMultiple) {
×
70
      const newValue = value.includes(val) ? value.filter((v) => v !== val) : value.concat(val);
×
71
      return onChange?.(newValue);
×
72
    }
×
73
    onChange?.(val === value[0] ? undefined : val);
×
74
  };
×
75

×
76
  const checkIsActive = useCallback(
×
77
    (v: string) => {
×
78
      return isMultiple ? value.includes(v) : value[0] === v;
×
79
    },
×
80
    [isMultiple, value]
×
81
  );
×
82

×
83
  const onOptionAddInner = async () => {
×
84
    if (!searchValue) return;
×
85
    setSearchValue('');
×
86
    await onOptionAdd?.(searchValue);
×
87
    if (isMultiple) {
×
88
      const newValue = value.concat(searchValue);
×
89
      setValue(newValue);
×
90
      return onChange?.(newValue);
×
91
    }
×
92
    setValue([searchValue]);
×
93
    onChange?.(searchValue);
×
94
  };
×
95

×
96
  const addOptionText = `Add an option '${searchValue}'`;
×
97
  const optionAddable = searchValue && options.findIndex((v) => v.value === searchValue) === -1;
×
98

×
99
  return (
×
100
    <Command className={className} style={style}>
×
101
      <SearchInput
×
102
        reRef={inputRef}
×
103
        searchValue={searchValue}
×
104
        setSearchValue={setSearchValue}
×
105
        onOptionAdd={onOptionAddInner}
×
106
      />
×
107
      <CommandList>
×
108
        <CommandEmpty className="p-2">
×
109
          <Button variant={'ghost'} size={'sm'} className="w-full text-sm">
×
110
            <Plus className="size-4" />
×
111
            <span className="ml-2">{addOptionText}</span>
×
112
          </Button>
×
113
        </CommandEmpty>
×
114
        <CommandGroup aria-valuetext="name">
×
115
          {options.map(({ label, value, backgroundColor, color }) => (
×
116
            <CommandItem
×
117
              className="justify-between"
×
118
              key={value}
×
119
              value={value}
×
120
              onSelect={() => onSelect(value)}
×
121
            >
×
122
              <SelectTag
×
123
                label={label || 'Untitled'}
×
124
                backgroundColor={backgroundColor}
×
125
                color={color}
×
126
              />
×
127
              {checkIsActive(value) && <Check className={'ml-2 size-4'} />}
×
128
            </CommandItem>
×
129
          ))}
×
130
          <CommandItem
×
131
            className={cn('items-center justify-center', !optionAddable && 'opacity-0 h-0 p-0')}
×
132
            onSelect={onOptionAddInner}
×
133
          >
×
134
            <Plus className="size-4 shrink-0" />
×
135
            <span className="ml-2 truncate">{addOptionText}</span>
×
136
          </CommandItem>
×
137
        </CommandGroup>
×
138
      </CommandList>
×
139
    </Command>
×
140
  );
×
141
};
×
142

×
143
export const SelectEditorMain = forwardRef(SelectEditorMainBase);
×
144

×
145
const SearchInput = ({
×
146
  reRef,
×
147
  searchValue,
×
148
  setSearchValue,
×
149
  onOptionAdd,
×
150
}: {
×
151
  reRef: React.Ref<HTMLInputElement>;
×
152
  searchValue: string;
×
153
  setSearchValue: (value: string) => void;
×
154
  onOptionAdd: () => Promise<void>;
×
155
}) => {
×
156
  const isEmpty = useCommandState((state) => state.filtered.count === 1);
×
157

×
158
  return (
×
159
    <CommandInput
×
160
      ref={reRef}
×
161
      placeholder="Search option"
×
162
      value={searchValue}
×
163
      onValueChange={(value) => setSearchValue(value)}
×
164
      onKeyDown={async (e) => {
×
165
        if (e.key === 'Enter' && isEmpty) {
×
166
          e.stopPropagation();
×
167
          await onOptionAdd();
×
168
        }
×
169
      }}
×
170
    />
×
171
  );
×
172
};
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc