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

teableio / teable / 8538004962

03 Apr 2024 11:36AM UTC 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/hide-fields/HideFieldsBase.tsx
1
import { DraggableHandle } from '@teable/icons';
×
2
import {
×
3
  Switch,
×
4
  Label,
×
5
  Button,
×
6
  Popover,
×
7
  PopoverTrigger,
×
8
  PopoverContent,
×
9
  Command,
×
10
  CommandEmpty,
×
11
  CommandInput,
×
12
  CommandItem,
×
13
  CommandList,
×
14
  Tooltip,
×
15
  TooltipContent,
×
16
  TooltipProvider,
×
17
  TooltipTrigger,
×
18
} from '@teable/ui-lib';
×
19
import {
×
20
  DndKitContext,
×
21
  Droppable,
×
22
  Draggable,
×
23
  type DragEndEvent,
×
24
} from '@teable/ui-lib/src/base/dnd-kit';
×
25
import { map } from 'lodash';
×
26
import React, { useEffect, useMemo, useState, useCallback } from 'react';
×
27
import { useTranslation } from '../../context/app/i18n';
×
28
import { useFieldStaticGetter, useView } from '../../hooks';
×
29
import type { IFieldInstance } from '../../model';
×
30
import { swapReorder } from '../../utils/order';
×
31

×
32
interface IHideFieldsBaseProps {
×
33
  fields: IFieldInstance[];
×
34
  hidden: string[];
×
NEW
35
  footer?: React.ReactNode;
×
36
  children: React.ReactNode;
×
37
  onChange: (hidden: string[]) => void;
×
38
}
×
39

×
40
export const HideFieldsBase = (props: IHideFieldsBaseProps) => {
×
NEW
41
  const { fields, hidden, footer, children, onChange } = props;
×
42
  const fieldStaticGetter = useFieldStaticGetter();
×
43
  const view = useView();
×
44
  const [innerFields, setInnerFields] = useState([...fields]);
×
45
  const { t } = useTranslation();
×
46
  const [dragHandleVisible, setDragHandleVisible] = useState(true);
×
47

×
48
  useEffect(() => {
×
49
    setInnerFields([...fields]);
×
50
  }, [fields]);
×
51

×
52
  const statusMap = useMemo(() => {
×
53
    return fields.reduce(
×
54
      (acc, field) => {
×
55
        acc[field.id] = !hidden.includes(field.id);
×
56
        return acc;
×
57
      },
×
58
      {} as Record<string, boolean>
×
59
    );
×
60
  }, [fields, hidden]);
×
61

×
62
  const switchChange = (id: string, checked: boolean) => {
×
63
    if (checked) {
×
64
      onChange(hidden.filter((fieldId) => fieldId !== id));
×
65
      return;
×
66
    }
×
67
    onChange([...hidden, id]);
×
68
  };
×
69

×
70
  const showAll = () => {
×
71
    onChange([]);
×
72
  };
×
73

×
74
  const hideAll = () => {
×
75
    const hiddenFields = fields.filter((field) => !field.isPrimary);
×
76
    onChange(map(hiddenFields, 'id'));
×
77
  };
×
78

×
79
  const dragEndHandler = (event: DragEndEvent) => {
×
80
    const { over, active } = event;
×
81
    const to = over?.data?.current?.sortable?.index;
×
82
    const from = active?.data?.current?.sortable?.index;
×
83

×
84
    if (!over || !view || to === from) {
×
85
      return;
×
86
    }
×
87

×
88
    const list = [...fields];
×
89
    const [field] = list.splice(from, 1);
×
90

×
91
    const newOrder = swapReorder(1, from, to, fields.length, (index) => {
×
92
      const fieldId = fields[index].id;
×
93
      return view?.columnMeta[fieldId].order;
×
94
    })[0];
×
95

×
96
    if (newOrder === view?.columnMeta[field.id].order) {
×
97
      return;
×
98
    }
×
99

×
100
    list.splice(to, 0, field);
×
101
    setInnerFields(list);
×
102
    view.updateColumnMeta([
×
103
      {
×
104
        fieldId: field.id as string,
×
105
        columnMeta: {
×
106
          order: newOrder,
×
107
        },
×
108
      },
×
109
    ]);
×
110
  };
×
111
  const commandFilter = useCallback(
×
112
    (fieldId: string, searchValue: string) => {
×
113
      const currentField = fields.find(
×
114
        ({ id }) => fieldId.toLocaleLowerCase() === id.toLocaleLowerCase()
×
115
      );
×
116
      const name = currentField?.name?.toLocaleLowerCase() || t('common.untitled');
×
117
      const containWord = name.indexOf(searchValue.toLowerCase()) > -1;
×
118
      return Number(containWord);
×
119
    },
×
120
    [fields, t]
×
121
  );
×
122

×
123
  const searchHandle = (value: string) => {
×
124
    setDragHandleVisible(!value);
×
125
  };
×
126

×
127
  const content = () => (
×
NEW
128
    <div className="rounded-lg p-1">
×
129
      <Command filter={commandFilter}>
×
130
        <CommandInput
×
131
          placeholder="Search a field"
×
132
          className="h-8 text-xs"
×
133
          onValueChange={(value) => searchHandle(value)}
×
134
        />
×
NEW
135
        <CommandList className="my-2 max-h-64">
×
136
          <CommandEmpty>{t('common.search.empty')}</CommandEmpty>
×
137
          <DndKitContext onDragEnd={dragEndHandler}>
×
138
            <Droppable items={innerFields.map(({ id }) => ({ id }))}>
×
139
              {innerFields.map((field) => {
×
140
                const { id, name, type, isLookup, isPrimary } = field;
×
141
                const { Icon } = fieldStaticGetter(type, isLookup);
×
142
                return (
×
143
                  <Draggable key={id} id={id}>
×
144
                    {({ setNodeRef, listeners, attributes, style, isDragging }) => (
×
145
                      <>
×
146
                        {
×
147
                          <CommandItem
×
148
                            className="flex flex-1 p-0"
×
149
                            key={id}
×
150
                            value={id}
×
151
                            ref={setNodeRef}
×
152
                            style={{
×
153
                              ...style,
×
154
                              opacity: isDragging ? '0.6' : '1',
×
155
                            }}
×
156
                          >
×
157
                            <TooltipProvider>
×
158
                              <Tooltip>
×
159
                                <TooltipTrigger asChild>
×
160
                                  <div className="flex flex-1 items-center p-0">
×
161
                                    <Label
×
162
                                      htmlFor={id}
×
163
                                      className="flex flex-1 cursor-pointer items-center truncate p-2"
×
164
                                    >
×
165
                                      <Switch
×
166
                                        id={id}
×
167
                                        className="scale-75"
×
168
                                        checked={statusMap[id]}
×
169
                                        onCheckedChange={(checked) => {
×
170
                                          switchChange(id, checked);
×
171
                                        }}
×
172
                                        disabled={isPrimary}
×
173
                                      />
×
174
                                      <Icon className="ml-2 shrink-0" />
×
175
                                      <span className="h-full flex-1 cursor-pointer truncate pl-1 text-sm">
×
176
                                        {name}
×
177
                                      </span>
×
178
                                    </Label>
×
179
                                    {/* forbid drag when search */}
×
180
                                    {dragHandleVisible && (
×
181
                                      <div {...attributes} {...listeners} className="pr-1">
×
182
                                        <DraggableHandle></DraggableHandle>
×
183
                                      </div>
×
184
                                    )}
×
185
                                  </div>
×
186
                                </TooltipTrigger>
×
187
                                {isPrimary ? (
×
188
                                  <TooltipContent>
×
189
                                    <p>{t('hidden.forbidHiddenPrimaryTip')}</p>
×
190
                                  </TooltipContent>
×
191
                                ) : null}
×
192
                              </Tooltip>
×
193
                            </TooltipProvider>
×
194
                          </CommandItem>
×
195
                        }
×
196
                      </>
×
197
                    )}
×
198
                  </Draggable>
×
199
                );
×
200
              })}
×
201
            </Droppable>
×
202
          </DndKitContext>
×
203
        </CommandList>
×
204
      </Command>
×
205
      {dragHandleVisible && (
×
206
        <div className="flex justify-between p-2">
×
207
          <Button
×
208
            variant="secondary"
×
209
            size="xs"
×
210
            className="w-32 text-muted-foreground hover:text-secondary-foreground"
×
211
            onClick={showAll}
×
212
          >
×
213
            {t('hidden.showAll')}
×
214
          </Button>
×
215
          <Button
×
216
            variant="secondary"
×
217
            size="xs"
×
218
            className="w-32 text-muted-foreground hover:text-secondary-foreground"
×
219
            onClick={hideAll}
×
220
          >
×
221
            {t('hidden.hideAll')}
×
222
          </Button>
×
223
        </div>
×
224
      )}
×
225
    </div>
×
226
  );
×
227

×
228
  return (
×
229
    <Popover>
×
230
      <PopoverTrigger asChild>{children}</PopoverTrigger>
×
NEW
231
      <PopoverContent side="bottom" align="start" className="p-0">
×
232
        {content()}
×
NEW
233
        {footer}
×
234
      </PopoverContent>
×
235
    </Popover>
×
236
  );
×
237
};
×
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