@cdc/core 4.24.10 → 4.24.11

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.
Files changed (52) hide show
  1. package/components/AdvancedEditor/AdvancedEditor.tsx +17 -13
  2. package/components/Alert/components/Alert.tsx +34 -8
  3. package/components/DataTable/DataTable.tsx +12 -2
  4. package/components/DataTable/data-table.css +4 -22
  5. package/components/DataTable/helpers/boxplotCellMatrix.tsx +14 -13
  6. package/components/DataTable/helpers/getChartCellValue.ts +23 -5
  7. package/components/EditorPanel/ColumnsEditor.tsx +81 -36
  8. package/components/EditorPanel/DataTableEditor.tsx +33 -33
  9. package/components/EditorPanel/FieldSetWrapper.tsx +2 -2
  10. package/components/EditorPanel/Inputs.tsx +26 -16
  11. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +30 -55
  12. package/components/Filters/Filters.tsx +12 -4
  13. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +0 -4
  14. package/components/Layout/components/Visualization/visualizations.scss +1 -1
  15. package/components/Legend/Legend.Gradient.tsx +49 -34
  16. package/components/MultiSelect/MultiSelect.tsx +85 -62
  17. package/components/MultiSelect/multiselect.styles.css +10 -7
  18. package/components/NestedDropdown/NestedDropdown.tsx +40 -20
  19. package/components/NestedDropdown/nesteddropdown.styles.css +15 -13
  20. package/components/Table/Table.tsx +102 -34
  21. package/components/_stories/DataTable.stories.tsx +14 -0
  22. package/components/_stories/Filters.stories.tsx +57 -0
  23. package/components/_stories/_mocks/DataTable/no-data.json +108 -0
  24. package/components/ui/Icon.tsx +19 -6
  25. package/dist/cove-main.css +20 -54
  26. package/dist/cove-main.css.map +1 -1
  27. package/helpers/DataTransform.ts +2 -1
  28. package/helpers/cove/{number.js → number.ts} +25 -11
  29. package/helpers/fetchRemoteData.js +32 -37
  30. package/helpers/formatConfigBeforeSave.ts +1 -0
  31. package/helpers/queryStringUtils.ts +6 -0
  32. package/helpers/useDataVizClasses.ts +42 -20
  33. package/package.json +2 -2
  34. package/styles/_button-section.scss +1 -1
  35. package/styles/_global-variables.scss +3 -3
  36. package/styles/_global.scss +21 -22
  37. package/styles/_reset.scss +0 -11
  38. package/styles/filters.scss +0 -22
  39. package/styles/v2/base/_reset.scss +0 -7
  40. package/styles/v2/components/editor.scss +0 -4
  41. package/styles/v2/components/icon.scss +1 -1
  42. package/types/Axis.ts +2 -0
  43. package/types/BoxPlot.ts +5 -3
  44. package/types/Color.ts +1 -1
  45. package/types/Legend.ts +1 -2
  46. package/types/MarkupInclude.ts +1 -0
  47. package/types/Runtime.ts +3 -1
  48. package/types/Series.ts +8 -1
  49. package/types/Table.ts +1 -1
  50. package/types/Visualization.ts +7 -8
  51. package/components/ui/Select.jsx +0 -30
  52. package/helpers/getGradientLegendWidth.ts +0 -15
@@ -31,16 +31,6 @@ export type CheckboxProps = {
31
31
  } & Input &
32
32
  Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value'>
33
33
 
34
- export type SelectProps = {
35
- value?: string
36
- options?: string[]
37
- required?: boolean
38
- initial?: string
39
-
40
- // all other props
41
- [x: string]: any
42
- } & Input
43
-
44
34
  const TextField = memo((props: TextFieldProps) => {
45
35
  const {
46
36
  display = true,
@@ -141,6 +131,16 @@ const CheckBox = memo((props: CheckboxProps) => {
141
131
  )
142
132
  })
143
133
 
134
+ export type SelectProps = {
135
+ value?: string
136
+ options?: string[] | { label: string; value: string }[]
137
+ required?: boolean
138
+ initial?: string
139
+
140
+ // all other props
141
+ [x: string]: any
142
+ } & Input
143
+
144
144
  const Select = memo((props: SelectProps) => {
145
145
  const {
146
146
  display = true,
@@ -156,11 +156,21 @@ const Select = memo((props: SelectProps) => {
156
156
  initial: initialValue,
157
157
  ...attributes
158
158
  } = props
159
- let optionsJsx = options.map((optionName, index) => (
160
- <option value={optionName} key={index}>
161
- {optionName}
162
- </option>
163
- ))
159
+ const optionsJsx = options.map((option, index) => {
160
+ if (typeof option === 'string') {
161
+ return (
162
+ <option value={option} key={index}>
163
+ {option}
164
+ </option>
165
+ )
166
+ } else {
167
+ return (
168
+ <option value={option.value} key={index}>
169
+ {option.label}
170
+ </option>
171
+ )
172
+ }
173
+ })
164
174
 
165
175
  if (initialValue) {
166
176
  optionsJsx.unshift(
@@ -180,7 +190,7 @@ const Select = memo((props: SelectProps) => {
180
190
  {tooltip}
181
191
  </span>
182
192
  <select
183
- className={required && !value ? 'warning' : ''}
193
+ className={`cove-form-select ${required && !value ? 'warning' : ''}`}
184
194
  name={fieldName}
185
195
  value={value}
186
196
  onChange={event => {
@@ -140,43 +140,24 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
140
140
  controls={openControls}
141
141
  deleteField={() => removeFilter(filterIndex)}
142
142
  >
143
- <label>
144
- <span className='edit-label column-heading'>Filter Style</span>
145
-
146
- <select
147
- value={filter.filterStyle || 'dropdown'}
148
- onChange={e => {
149
- updateFilterStyle(filterIndex, e.target.value)
150
- }}
151
- >
152
- {filterStyleOptions.map((item, index) => {
153
- return (
154
- <option key={`filter-style-${index}`} value={item}>
155
- {item}
156
- </option>
157
- )
158
- })}
159
- </select>
160
- </label>
143
+ <Select
144
+ value={filter.filterStyle}
145
+ fieldName='filterStyle'
146
+ label='Filter Style'
147
+ updateField={(_section, _subsection, _field, value) => updateFilterStyle(filterIndex, value)}
148
+ options={filterStyleOptions}
149
+ />
161
150
 
162
151
  {filter.filterStyle !== 'nested-dropdown' ? (
163
152
  <>
164
- <label>
165
- <span className='edit-label column-heading'>Filter</span>
166
- <select
167
- value={filter.columnName}
168
- onChange={e => {
169
- handleNameChange(filterIndex, e.target.value)
170
- }}
171
- >
172
- <option value=''>- Select Option -</option>
173
- {dataColumns.map((dataKey, filterIndex) => (
174
- <option value={dataKey} key={filterIndex}>
175
- {dataKey}
176
- </option>
177
- ))}
178
- </select>
179
- </label>
153
+ <Select
154
+ value={filter.columnName}
155
+ fieldName='columnName'
156
+ label='Filter'
157
+ updateField={(_section, _subsection, _field, value) => handleNameChange(filterIndex, value)}
158
+ options={dataColumns}
159
+ initial='- Select Option -'
160
+ />
180
161
 
181
162
  <label>
182
163
  <span className='edit-showDropdown column-heading'>Show Filter Input</span>
@@ -233,27 +214,21 @@ const VizFilterEditor: React.FC<VizFilterProps> = ({ config, updateField, rawDat
233
214
  />
234
215
  </label>
235
216
 
236
- <label>
237
- <span className='edit-filterOrder column-heading'>Filter Order</span>
238
- <select
239
- value={filter.order ? filter.order : 'asc'}
240
- onChange={e => updateFilterProp('order', filterIndex, e.target.value)}
241
- >
242
- {filterOrderOptions.map((option, index) => {
243
- return (
244
- <option value={option.value} key={`filter-${index}`}>
245
- {option.label}
246
- </option>
247
- )
248
- })}
249
- </select>
250
- {filter.order === 'cust' && (
251
- <FilterOrder
252
- orderedValues={filter.orderedValues || filter.values}
253
- handleFilterOrder={(index1, index2) => handleFilterOrder(index1, index2, filterIndex)}
254
- />
255
- )}
256
- </label>
217
+ <Select
218
+ value={filter.order || 'asc'}
219
+ fieldName='order'
220
+ label='Filter Order'
221
+ updateField={(_section, _subSection, _field, value) =>
222
+ updateFilterProp('order', filterIndex, value)
223
+ }
224
+ options={filterOrderOptions}
225
+ />
226
+ {filter.order === 'cust' && (
227
+ <FilterOrder
228
+ orderedValues={filter.orderedValues || filter.values}
229
+ handleFilterOrder={(index1, index2) => handleFilterOrder(index1, index2, filterIndex)}
230
+ />
231
+ )}
257
232
  </>
258
233
  ) : (
259
234
  <NestedDropdownEditor
@@ -351,7 +351,7 @@ const Filters = (props: FilterProps) => {
351
351
  id={`filter-${outerIndex}`}
352
352
  name={label}
353
353
  aria-label={`Filter by ${label}`}
354
- className='filter-select'
354
+ className='cove-form-select'
355
355
  data-index='0'
356
356
  value={active}
357
357
  onChange={e => {
@@ -439,6 +439,7 @@ const Filters = (props: FilterProps) => {
439
439
 
440
440
  const classList = [
441
441
  'single-filters',
442
+ 'form-group mr-3',
442
443
  mobileFilterStyle ? 'single-filters--dropdown' : `single-filters--${filterStyle}`
443
444
  ]
444
445
  const mobileExempt = ['nested-dropdown', 'multi-select'].includes(filterStyle)
@@ -446,7 +447,11 @@ const Filters = (props: FilterProps) => {
446
447
  return (
447
448
  <div className={classList.join(' ')} key={outerIndex}>
448
449
  <>
449
- {label && <label htmlFor={`filter-${outerIndex}`}>{label}</label>}
450
+ {label && (
451
+ <label className='text-capitalize font-weight-bold mt-1 mb-0' htmlFor={`filter-${outerIndex}`}>
452
+ {label}
453
+ </label>
454
+ )}
450
455
  {filterStyle === 'tab' && !mobileFilterStyle && Tabs}
451
456
  {filterStyle === 'pill' && !mobileFilterStyle && Pills}
452
457
  {filterStyle === 'tab bar' && !mobileFilterStyle && <TabBar filter={singleFilter} index={outerIndex} />}
@@ -463,6 +468,7 @@ const Filters = (props: FilterProps) => {
463
468
  <NestedDropdown
464
469
  activeGroup={(singleFilter.active as string) || (singleFilter.queuedActive || [])[0]}
465
470
  activeSubGroup={(singleFilter.subGrouping?.active as string) || (singleFilter.queuedActive || [])[1]}
471
+ filterIndex={outerIndex}
466
472
  options={getNestedOptions(singleFilter)}
467
473
  listLabel={label}
468
474
  handleSelectedItems={value => changeFilterActive(outerIndex, value)}
@@ -500,14 +506,16 @@ const Filters = (props: FilterProps) => {
500
506
  {filters?.some(filter => filter.active && filter.columnName) ? filterConstants.introText : ''}{' '}
501
507
  {visualizationConfig.filterBehavior === 'Apply Button' && filterConstants.applyText}
502
508
  </p>
503
- <div className='filters-section__wrapper'>
509
+ <div className='d-flex flex-wrap w-100 filters-section__wrapper'>
504
510
  {' '}
505
511
  <>
506
512
  <Style />
507
513
  {filterBehavior === 'Apply Button' ? (
508
514
  <div className='filters-section__buttons'>
509
515
  <Button
510
- onClick={() => handleApplyButton(filters)}
516
+ onClick={e => {
517
+ handleApplyButton(filters)
518
+ }}
511
519
  disabled={!showApplyButton}
512
520
  className={[general?.headerColor ? general.headerColor : theme, 'apply'].join(' ')}
513
521
  >
@@ -542,10 +542,6 @@
542
542
  font-weight: normal;
543
543
  }
544
544
 
545
- .btn {
546
- margin-top: 1em;
547
- }
548
-
549
545
  .sort-list {
550
546
  list-style: none;
551
547
 
@@ -1,6 +1,6 @@
1
1
  .cdc-open-viz-module {
2
2
  .cdc-chart-inner-container .cove-component__content {
3
- padding: 25px 15px !important;
3
+ padding: 25px 0px !important;
4
4
  }
5
5
  &.isEditor {
6
6
  overflow: auto;
@@ -1,11 +1,14 @@
1
1
  import { Group } from '@visx/group'
2
2
  import { Text } from '@visx/text'
3
- import { type ViewportSize, type MapConfig } from '@cdc/map/src/types/MapConfig'
3
+ import { type MapConfig } from '@cdc/map/src/types/MapConfig'
4
4
  import { type ChartConfig } from '@cdc/chart/src/types/ChartConfig'
5
- import { getGradientLegendWidth } from '@cdc/core/helpers/getGradientLegendWidth'
6
5
  import { getTextWidth } from '../../helpers/getTextWidth'
7
6
  import { DimensionsType } from '../../types/Dimensions'
8
7
 
8
+ const MARGIN = 1
9
+ const BORDER_SIZE = 1
10
+ const MOBILE_BREAKPOINT = 576
11
+
9
12
  type CombinedConfig = MapConfig | ChartConfig
10
13
 
11
14
  interface GradientProps {
@@ -13,30 +16,40 @@ interface GradientProps {
13
16
  colors: string[]
14
17
  config: CombinedConfig
15
18
  dimensions: DimensionsType
16
- currentViewport: ViewportSize
19
+ parentPaddingToSubtract?: number
17
20
  }
18
21
 
19
- const LegendGradient = ({ labels, colors, config, dimensions, currentViewport }: GradientProps): JSX.Element => {
22
+ const LegendGradient = ({
23
+ labels,
24
+ colors,
25
+ config,
26
+ dimensions,
27
+ parentPaddingToSubtract = 0
28
+ }: GradientProps): JSX.Element => {
29
+ const { uid, legend, type } = config
30
+ const { tickRotation, position, style, subStyle, hideBorder } = legend
31
+
32
+ const isLinearBlocks = subStyle === 'linear blocks'
20
33
  let [width] = dimensions
21
34
 
22
- const legendWidth = getGradientLegendWidth(width, currentViewport)
23
- const uniqueID = `${config.uid}-${Date.now()}`
35
+ const smallScreen = width <= MOBILE_BREAKPOINT
36
+ const legendWidth = Number(width) - parentPaddingToSubtract - MARGIN * 2 - BORDER_SIZE * 2
37
+ const uniqueID = `${uid}-${Date.now()}`
24
38
 
25
39
  const numTicks = colors?.length
26
40
 
27
41
  const longestLabel = labels && labels.length > 0 ? labels.reduce((a, b) => (a.length > b.length ? a : b)) : ''
28
42
  const boxHeight = 20
29
43
  let height = 50
30
- const margin = 1
31
44
 
32
45
  // configure tick witch and angle
33
46
  const textWidth = getTextWidth(longestLabel, `normal 14px sans-serif`)
34
- const rotationAngle = Number(config.legend.tickRotation) || 0
47
+ const rotationAngle = Number(tickRotation) || 0
35
48
  // Convert the angle from degrees to radians
36
49
  const angleInRadians = rotationAngle * (Math.PI / 180)
37
50
  const newHeight = height + Number(textWidth) * Math.sin(angleInRadians)
38
51
 
39
- // configre gradient colors
52
+ // configure gradient colors
40
53
  const stops = colors.map((color, index) => {
41
54
  const offset = (index / (colors.length - 1)) * 100
42
55
  return <stop key={index} offset={`${offset}%`} style={{ stopColor: color, stopOpacity: 1 }} />
@@ -45,69 +58,71 @@ const LegendGradient = ({ labels, colors, config, dimensions, currentViewport }:
45
58
  // render ticks and labels
46
59
  const ticks = labels.map((key, index) => {
47
60
  const segmentWidth = legendWidth / numTicks
48
- const xPositionX = index * segmentWidth + segmentWidth
61
+ const xPositionX = index * segmentWidth + segmentWidth + MARGIN
49
62
  const textAnchor = rotationAngle ? 'end' : 'middle'
50
63
  const verticalAnchor = rotationAngle ? 'middle' : 'start'
64
+ const lastTick = index === labels.length - 1
51
65
 
52
66
  return (
53
- <Group top={margin}>
54
- <line x1={xPositionX} x2={xPositionX} y1={30} y2={boxHeight} stroke='black' />
67
+ <Group top={MARGIN}>
68
+ {!lastTick && !isLinearBlocks && <line x1={xPositionX} x2={xPositionX} y1={30} y2={boxHeight} stroke='black' />}
55
69
  <Text
56
- angle={-config.legend.tickRotation}
70
+ angle={-tickRotation}
57
71
  x={xPositionX}
58
72
  y={boxHeight}
59
73
  dy={10}
60
74
  dx={-segmentWidth / 2}
61
- fontSize='14'
75
+ fontSize={smallScreen ? '12' : '14'}
62
76
  textAnchor={textAnchor}
63
77
  verticalAnchor={verticalAnchor}
78
+ width={segmentWidth}
79
+ lineHeight={'14'}
64
80
  >
65
81
  {key}
66
82
  </Text>
67
83
  </Group>
68
84
  )
69
85
  })
70
- if ((config.type === 'map' && config.legend.position === 'side') || !config.legend.position) {
86
+ if ((type === 'map' && position === 'side') || !position) {
71
87
  return
72
88
  }
73
- if (
74
- config.type === 'chart' &&
75
- (config.legend.position === 'left' || config.legend.position === 'right' || !config.legend.position)
76
- ) {
89
+ if (type === 'chart' && (position === 'left' || position === 'right' || !position)) {
77
90
  return
78
91
  }
79
92
 
80
- if (config.legend.style === 'gradient') {
93
+ if (style === 'gradient') {
81
94
  return (
82
- <svg style={{ overflow: 'visible', width: '100%', marginTop: 10 }} height={newHeight}>
95
+ <svg
96
+ style={{ overflow: 'visible', width: '100%', marginTop: 10, marginBottom: hideBorder ? 10 : 0 }}
97
+ height={newHeight}
98
+ >
83
99
  {/* background border*/}
84
- <rect
85
- x={0}
86
- y={0}
87
- width={legendWidth + margin * 2}
88
- height={boxHeight + margin * 2}
89
- fill='#d3d3d3'
90
- strokeWidth='0.5'
91
- />
100
+ <rect x={0} y={0} width={legendWidth + MARGIN * 2} height={boxHeight + MARGIN * 2} fill='#d3d3d3' />
92
101
  {/* Define the gradient */}
93
102
  <linearGradient id={`gradient-smooth-${uniqueID}`} x1='0%' y1='0%' x2='100%' y2='0%'>
94
103
  {stops}
95
104
  </linearGradient>
96
105
 
97
- {config.legend.subStyle === 'smooth' && (
98
- <rect x={1} y={1} width={legendWidth} height={boxHeight} fill={`url(#gradient-smooth-${uniqueID})`} />
106
+ {subStyle === 'smooth' && (
107
+ <rect
108
+ x={MARGIN}
109
+ y={MARGIN}
110
+ width={legendWidth}
111
+ height={boxHeight}
112
+ fill={`url(#gradient-smooth-${uniqueID})`}
113
+ />
99
114
  )}
100
115
 
101
- {config.legend.subStyle === 'linear blocks' &&
116
+ {subStyle === 'linear blocks' &&
102
117
  colors.map((color, index) => {
103
118
  const segmentWidth = legendWidth / numTicks
104
- const xPosition = index * segmentWidth
119
+ const xPosition = index * segmentWidth + MARGIN
105
120
  return (
106
121
  <Group>
107
122
  <rect
108
123
  key={index}
109
124
  x={xPosition}
110
- y={0}
125
+ y={MARGIN}
111
126
  width={segmentWidth}
112
127
  height={boxHeight}
113
128
  fill={color}
@@ -22,7 +22,17 @@ interface MultiSelectProps {
22
22
  tooltip?: React.ReactNode
23
23
  }
24
24
 
25
- const MultiSelect: React.FC<MultiSelectProps> = ({ section = null, subsection = null, fieldName, label, options, updateField, selected = [], limit, tooltip }) => {
25
+ const MultiSelect: React.FC<MultiSelectProps> = ({
26
+ section = null,
27
+ subsection = null,
28
+ fieldName,
29
+ label,
30
+ options,
31
+ updateField,
32
+ selected = [],
33
+ limit,
34
+ tooltip
35
+ }) => {
26
36
  const preselectedItems = options.filter(opt => selected.includes(opt.value)).slice(0, limit)
27
37
  const [selectedItems, setSelectedItems] = useState<Option[]>(preselectedItems)
28
38
  const [expanded, setExpanded] = useState(false)
@@ -67,77 +77,90 @@ const MultiSelect: React.FC<MultiSelectProps> = ({ section = null, subsection =
67
77
 
68
78
  const multiID = 'multiSelect_' + label
69
79
  return (
70
- <div ref={multiSelectRef} className='cove-multiselect'>
80
+ <>
71
81
  {label && (
72
- <span id={multiID} className='edit-label column-heading'>
82
+ <label className='text-capitalize font-weight-bold' id={multiID + label} htmlFor={multiID}>
73
83
  {label}
74
- </span>
84
+ </label>
75
85
  )}
86
+ <div ref={multiSelectRef} className='cove-multiselect'>
87
+ {tooltip && tooltip}
76
88
 
77
- {tooltip && tooltip}
78
-
79
- <div className='wrapper'>
80
- <div className='selected'>
81
- {selectedItems.map(item => (
82
- <div key={item.value} aria-labelledby={label ? multiID : undefined}>
83
- {item.label}
84
- <button
85
- aria-label='Remove'
86
- onClick={e => {
87
- e.preventDefault()
88
- handleItemRemove(item)
89
- }}
90
- onKeyUp={e => {
91
- handleItemRemove(item, e)
92
- }}
93
- >
94
- x
95
- </button>
96
- </div>
97
- ))}
98
- <button
99
- aria-label={expanded ? 'Collapse' : 'Expand'}
100
- aria-labelledby={label ? multiID : undefined}
101
- className='expand'
102
- onClick={e => {
103
- e.preventDefault()
104
- setExpanded(!expanded)
89
+ <div className='wrapper'>
90
+ <div
91
+ id={multiID}
92
+ onClick={() => {
93
+ if (!selectedItems.length) {
94
+ setExpanded(true)
95
+ }
105
96
  }}
97
+ className='selected'
106
98
  >
107
- <Icon display={expanded ? 'caretDown' : 'caretUp'} style={{ cursor: 'pointer' }} />
108
- </button>
109
- </div>
110
- {!!limit && (
111
- <Tooltip style={{ textTransform: 'none' }}>
112
- <Tooltip.Target>
113
- <Icon display='question' style={{ marginLeft: '0.5rem' }} />
114
- </Tooltip.Target>
115
- <Tooltip.Content>
116
- <p>Select up to {limit} items</p>
117
- </Tooltip.Content>
118
- </Tooltip>
119
- )}
120
- </div>
121
- <ul className={'dropdown' + (expanded ? '' : ' d-none')}>
122
- {options
123
- .filter(option => !selectedItems.find(item => item.value === option.value))
124
- .map(option => (
125
- <li
126
- className='cove-multiselect-li'
127
- key={option.value}
128
- role='option'
129
- tabIndex={0}
99
+ {selectedItems.length ? (
100
+ selectedItems.map(item => (
101
+ <div key={item.value} aria-labelledby={label ? multiID + label : undefined}>
102
+ {item.label}
103
+ <button
104
+ aria-label='Remove'
105
+ onClick={e => {
106
+ e.preventDefault()
107
+ handleItemRemove(item)
108
+ }}
109
+ onKeyUp={e => {
110
+ handleItemRemove(item, e)
111
+ }}
112
+ >
113
+ x
114
+ </button>
115
+ </div>
116
+ ))
117
+ ) : (
118
+ <span className='pl-1 pt-1'>- Select -</span>
119
+ )}
120
+ <button
121
+ aria-label={expanded ? 'Collapse' : 'Expand'}
122
+ aria-labelledby={label ? multiID : undefined}
123
+ className='expand'
130
124
  onClick={e => {
131
125
  e.preventDefault()
132
- handleItemSelect(option, e)
126
+ setExpanded(!expanded)
133
127
  }}
134
- onKeyUp={e => handleItemSelect(option, e)}
135
128
  >
136
- {option.label}
137
- </li>
138
- ))}
139
- </ul>
140
- </div>
129
+ <Icon display={'caretDown'} style={{ cursor: 'pointer' }} />
130
+ </button>
131
+ </div>
132
+ {!!limit && (
133
+ <Tooltip style={{ textTransform: 'none' }}>
134
+ <Tooltip.Target>
135
+ <Icon display='question' style={{ marginLeft: '0.5rem' }} />
136
+ </Tooltip.Target>
137
+ <Tooltip.Content>
138
+ <p>Select up to {limit} items</p>
139
+ </Tooltip.Content>
140
+ </Tooltip>
141
+ )}
142
+ </div>
143
+ <ul className={'dropdown' + (expanded ? '' : ' d-none')}>
144
+ {options
145
+ .filter(option => !selectedItems.find(item => item.value === option.value))
146
+ .map(option => (
147
+ <li
148
+ className='cove-multiselect-li'
149
+ key={option.value}
150
+ role='option'
151
+ tabIndex={0}
152
+ onClick={e => {
153
+ e.preventDefault()
154
+ handleItemSelect(option, e)
155
+ }}
156
+ onKeyUp={e => handleItemSelect(option, e)}
157
+ >
158
+ {option.label}
159
+ </li>
160
+ ))}
161
+ </ul>
162
+ </div>
163
+ </>
141
164
  )
142
165
  }
143
166
 
@@ -1,5 +1,4 @@
1
1
  .cove-multiselect {
2
- position: relative;
3
2
  .cove-input__label {
4
3
  display: block;
5
4
  }
@@ -8,8 +7,7 @@
8
7
  align-items: center;
9
8
  .selected {
10
9
  border: 1px solid var(--lightGray);
11
- padding: 5px;
12
- min-height: 40px;
10
+ padding: 7px;
13
11
  min-width: 200px;
14
12
  display: inline-block;
15
13
  :is(button) {
@@ -25,10 +23,14 @@
25
23
  border-radius: 5px;
26
24
  }
27
25
  .expand {
28
- padding: 0 5px;
29
- border-radius: 5px;
30
- background: var(--lightGray);
26
+ padding: 2px 0px;
27
+ margin-right: -6px;
31
28
  float: right;
29
+ margin-bottom: -3px;
30
+ color: var(--mediumGray);
31
+ &:focus {
32
+ outline: none;
33
+ }
32
34
  }
33
35
  border-radius: 5px;
34
36
  }
@@ -43,7 +45,8 @@
43
45
  .dropdown {
44
46
  background: white;
45
47
  position: absolute;
46
- margin-top: 5px;
48
+ top: var(--select-height);
49
+ margin-top: 0px;
47
50
  border: 1px solid var(--lightGray);
48
51
  padding-left: 0;
49
52
  min-height: 40px;