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

code4recovery / tsml-ui / 15263459658

26 May 2025 11:31PM UTC coverage: 59.518% (-0.03%) from 59.548%
15263459658

push

github

joshreisner
style fixes for SF

521 of 960 branches covered (54.27%)

Branch coverage included in aggregate %.

3 of 6 new or added lines in 2 files covered. (50.0%)

2 existing lines in 2 files now uncovered.

664 of 1031 relevant lines covered (64.4%)

8.99 hits per line

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

84.62
/src/components/Dropdown.tsx
1
import {
2
  Dispatch,
3
  Fragment,
4
  MouseEvent,
5
  SetStateAction,
6
  useState,
7
} from 'react';
8

9
import { useSearchParams } from 'react-router-dom';
10

11
import { getIndexByKey, formatString as i18n, useSettings } from '../helpers';
12
import { dropdownButtonCss, dropdownCss } from '../styles';
13

14
import type { Index, State } from '../types';
15

16
export default function Dropdown({
17
  defaultValue,
18
  filter,
19
  open,
20
  setDropdown,
21
  state,
22
}: {
23
  defaultValue: string;
24
  filter: keyof State['indexes'];
25
  open: boolean;
26
  setDropdown: Dispatch<SetStateAction<string | undefined>>;
27
  state: State;
28
}) {
29
  const [searchParams, setSearchParams] = useSearchParams();
9✔
30
  const { strings } = useSettings();
9✔
31
  const options = state.indexes[filter];
9✔
32
  const values = state.input[filter];
9✔
33
  const [expanded, setExpanded] = useState<string[]>([]);
9✔
34

35
  //handle expand toggle
36
  const toggleExpanded = (e: MouseEvent<HTMLButtonElement>, key: string) => {
9✔
UNCOV
37
    e.preventDefault();
×
38
    e.stopPropagation();
×
39
    if (!expanded.includes(key)) {
×
40
      setExpanded(expanded.concat(key));
×
41
    } else {
NEW
42
      setExpanded(expanded.filter(item => item !== key));
×
43
    }
44
  };
45

46
  //set filter: pass it up to parent
47
  const setFilter = (
9✔
48
    e: MouseEvent<HTMLButtonElement>,
49
    filter: keyof typeof state.indexes,
50
    value?: string
51
  ) => {
52
    e.preventDefault();
4✔
53

54
    // add or remove from filters
55
    let currentValues = searchParams.get(filter)?.split('/') ?? [];
4✔
56

57
    if (value) {
4✔
58
      const index = currentValues.indexOf(value);
3✔
59
      if (e.metaKey || e.ctrlKey) {
3✔
60
        if (index === -1) {
2✔
61
          currentValues.push(value);
1✔
62
        } else {
63
          // Remove the value
64
          currentValues.splice(index, 1);
1✔
65
        }
66
        // sort values
67
        if (currentValues.length) {
2!
68
          currentValues.sort();
2✔
69

70
          // TODO: this is a hack to get around unable to use %2F in search params
71
          // currently this will break if filter values are seperated by escaping / with  %2F
72
          const newValues = currentValues.join('/');
2✔
73
          searchParams.set(filter, newValues);
2✔
74
        } else {
75
          searchParams.delete(filter);
×
76
        }
77
      } else {
78
        // Single value, directly set the value
79
        searchParams.set(filter, value);
1✔
80
      }
81
    } else {
82
      // Remove the filter from search params if no value is provided
83
      searchParams.delete(filter);
1✔
84
    }
85

86
    // Update search params state
87
    setSearchParams(searchParams);
4✔
88
  };
89

90
  const renderDropdownItem = (
9✔
91
    { key, name, slugs, children }: Index,
92
    parentExpanded: boolean = true
14✔
93
  ) => (
94
    <Fragment key={key}>
16✔
95
      <div
96
        className="tsml-dropdown__item"
97
        // @ts-expect-error TODO
98
        data-active={values.includes(key)}
99
      >
100
        <button
101
          className="tsml-dropdown__button"
102
          onClick={e => setFilter(e, filter, key)}
3✔
103
          tabIndex={parentExpanded ? 0 : -1}
16✔
104
        >
105
          <span>{name}</span>
106
          <span
107
            aria-label={
108
              slugs.length === 1
16!
109
                ? strings.match_single
110
                : i18n(strings.match_multiple, {
111
                    count: slugs.length,
112
                  })
113
            }
114
          >
115
            {slugs.length}
116
          </span>
117
        </button>
118
        {!!children?.length && (
18✔
119
          <button
120
            className="tsml-dropdown__expand"
121
            data-expanded={expanded.includes(key)}
NEW
122
            onClick={e => toggleExpanded(e, key)}
×
123
            aria-label={
124
              expanded.includes(key) ? strings.collapse : strings.expand
2!
125
            }
126
          ></button>
127
        )}
128
      </div>
129
      {!!children?.length && (
18✔
130
        <div
131
          className="tsml-dropdown__children"
132
          data-expanded={expanded.includes(key)}
133
        >
134
          {children.map(child =>
135
            renderDropdownItem(child, expanded.includes(key))
2✔
136
          )}
137
        </div>
138
      )}
139
    </Fragment>
140
  );
141

142
  //separate section above the other items
143
  const special = {
9✔
144
    type: ['active', 'in-person', 'online'],
145
  };
146

147
  return (
9✔
148
    <div css={dropdownCss}>
149
      <button
150
        aria-expanded={open}
151
        css={dropdownButtonCss}
152
        id={filter}
153
        onClick={e => {
154
          setDropdown(open ? undefined : filter);
3✔
155
          e.stopPropagation();
3✔
156
        }}
157
      >
158
        {values?.length && options?.length
19✔
159
          ? values.map(value => getIndexByKey(options, value)?.name).join(' + ')
1✔
160
          : defaultValue}
161
      </button>
162
      <div
163
        aria-labelledby={filter}
164
        className="tsml-dropdown"
165
        style={{ display: open ? 'block' : 'none' }}
9✔
166
      >
167
        <div data-active={!values.length} className="tsml-dropdown__item">
168
          <button
169
            className="tsml-dropdown__button"
170
            onClick={e => setFilter(e, filter, undefined)}
1✔
171
          >
172
            {defaultValue}
173
          </button>
174
        </div>
175
        {[
176
          options
177
            ?.filter(option =>
178
              special[filter as keyof typeof special]?.includes(option.key)
14✔
179
            )
180
            .sort(
181
              (a, b) =>
182
                special[filter as keyof typeof special]?.indexOf(a.key) -
2✔
183
                special[filter as keyof typeof special]?.indexOf(b.key)
184
            ),
185
          options?.filter(
186
            option =>
187
              !special[filter as keyof typeof special]?.includes(option.key)
14✔
188
          ),
189
        ]
190
          .filter(e => e.length)
18✔
191
          .map((group, index) => (
192
            <Fragment key={index}>
10✔
193
              <hr />
194
              {group.map(option => renderDropdownItem(option))}
14✔
195
            </Fragment>
196
          ))}
197
      </div>
198
    </div>
199
  );
200
}
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

© 2026 Coveralls, Inc