@cfpb/cfpb-design-system 4.0.3 → 4.1.0

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 (92) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/dist/base/index.css +1 -1
  3. package/dist/base/index.css.map +2 -2
  4. package/dist/base/index.js +1 -1
  5. package/dist/base/index.js.map +1 -1
  6. package/dist/components/cfpb-buttons/index.css +1 -1
  7. package/dist/components/cfpb-buttons/index.css.map +2 -2
  8. package/dist/components/cfpb-buttons/index.js +1 -1
  9. package/dist/components/cfpb-buttons/index.js.map +1 -1
  10. package/dist/components/cfpb-expandables/index.css +1 -1
  11. package/dist/components/cfpb-expandables/index.css.map +2 -2
  12. package/dist/components/cfpb-expandables/index.js +1 -1
  13. package/dist/components/cfpb-expandables/index.js.map +1 -1
  14. package/dist/components/cfpb-forms/index.css +1 -1
  15. package/dist/components/cfpb-forms/index.css.map +2 -2
  16. package/dist/components/cfpb-forms/index.js +1 -1
  17. package/dist/components/cfpb-forms/index.js.map +1 -1
  18. package/dist/components/cfpb-icons/index.css +1 -1
  19. package/dist/components/cfpb-icons/index.css.map +2 -2
  20. package/dist/components/cfpb-icons/index.js +1 -1
  21. package/dist/components/cfpb-icons/index.js.map +1 -1
  22. package/dist/components/cfpb-layout/index.css +1 -1
  23. package/dist/components/cfpb-layout/index.css.map +2 -2
  24. package/dist/components/cfpb-layout/index.js +1 -1
  25. package/dist/components/cfpb-layout/index.js.map +1 -1
  26. package/dist/components/cfpb-notifications/index.css +1 -1
  27. package/dist/components/cfpb-notifications/index.css.map +2 -2
  28. package/dist/components/cfpb-notifications/index.js +1 -1
  29. package/dist/components/cfpb-notifications/index.js.map +1 -1
  30. package/dist/components/cfpb-pagination/index.css +1 -1
  31. package/dist/components/cfpb-pagination/index.css.map +2 -2
  32. package/dist/components/cfpb-pagination/index.js +1 -1
  33. package/dist/components/cfpb-pagination/index.js.map +1 -1
  34. package/dist/components/cfpb-tables/index.css +1 -1
  35. package/dist/components/cfpb-tables/index.css.map +2 -2
  36. package/dist/components/cfpb-tables/index.js +1 -1
  37. package/dist/components/cfpb-tables/index.js.map +1 -1
  38. package/dist/components/cfpb-tooltips/index.css +1 -1
  39. package/dist/components/cfpb-tooltips/index.css.map +2 -2
  40. package/dist/components/cfpb-tooltips/index.js +1 -1
  41. package/dist/components/cfpb-tooltips/index.js.map +1 -1
  42. package/dist/components/cfpb-typography/index.css +1 -1
  43. package/dist/components/cfpb-typography/index.css.map +2 -2
  44. package/dist/components/cfpb-typography/index.js +1 -1
  45. package/dist/components/cfpb-typography/index.js.map +1 -1
  46. package/dist/elements/cfpb-button/index.js +12 -4
  47. package/dist/elements/cfpb-button/index.js.map +4 -4
  48. package/dist/elements/cfpb-file-upload/index.js +11 -4
  49. package/dist/elements/cfpb-file-upload/index.js.map +4 -4
  50. package/dist/elements/cfpb-form-choice/index.js +11 -3
  51. package/dist/elements/cfpb-form-choice/index.js.map +4 -4
  52. package/dist/elements/cfpb-label/index.js +36 -0
  53. package/dist/elements/cfpb-label/index.js.map +7 -0
  54. package/dist/elements/cfpb-multiselect/index.js +13 -4
  55. package/dist/elements/cfpb-multiselect/index.js.map +4 -4
  56. package/dist/elements/cfpb-tag-filter/index.js +2 -2
  57. package/dist/elements/cfpb-tag-filter/index.js.map +2 -2
  58. package/dist/elements/cfpb-tag-group/index.js +2 -2
  59. package/dist/elements/cfpb-tag-group/index.js.map +2 -2
  60. package/dist/elements/cfpb-tag-topic/index.js +3 -3
  61. package/dist/elements/cfpb-tag-topic/index.js.map +2 -2
  62. package/dist/elements/index.js +14 -5
  63. package/dist/elements/index.js.map +4 -4
  64. package/dist/index.css +1 -1
  65. package/dist/index.css.map +2 -2
  66. package/dist/index.js +14 -5
  67. package/dist/index.js.map +4 -4
  68. package/dist/utilities/index.css +1 -1
  69. package/dist/utilities/index.css.map +2 -2
  70. package/dist/utilities/index.js +1 -1
  71. package/dist/utilities/index.js.map +1 -1
  72. package/package.json +1 -1
  73. package/src/abstracts/heading-mixins.scss +6 -0
  74. package/src/abstracts/vars.scss +23 -0
  75. package/src/base/base.scss +1 -1
  76. package/src/elements/cfpb-button/cfpb-button.component.scss +8 -0
  77. package/src/elements/cfpb-button/index.js +102 -19
  78. package/src/elements/cfpb-file-upload/index.js +6 -2
  79. package/src/elements/cfpb-form-choice/cfpb-form-choice.component.scss +6 -1
  80. package/src/elements/cfpb-form-choice/index.js +62 -29
  81. package/src/elements/cfpb-form-choice/index.spec.js +47 -0
  82. package/src/elements/cfpb-label/cfpb-label.component.scss +36 -0
  83. package/src/elements/cfpb-label/index.js +61 -0
  84. package/src/elements/cfpb-multiselect/cfpb-multiselect.component.scss +225 -0
  85. package/src/elements/cfpb-multiselect/index.js +444 -0
  86. package/src/elements/cfpb-multiselect/multiselect-model.js +288 -0
  87. package/src/elements/cfpb-multiselect/multiselect-model.spec.js +236 -0
  88. package/src/elements/cfpb-tag-filter/index.js +1 -1
  89. package/src/elements/cfpb-tag-filter/index.spec.js +1 -1
  90. package/src/elements/cfpb-tag-group/index.js +2 -1
  91. package/src/elements/cfpb-tag-topic/index.js +6 -0
  92. package/src/elements/index.js +2 -0
@@ -1,6 +1,13 @@
1
1
  import { html, LitElement, css, unsafeCSS } from 'lit';
2
+ import { classMap } from 'lit/directives/class-map.js';
2
3
  import styles from './cfpb-button.component.scss';
3
4
 
5
+ // The variants are different color themes of the button.
6
+ const VALID_VARIANTS = ['primary', 'secondary', 'warning'];
7
+
8
+ // The types are a regular button, or submit/reset that are used in forms.
9
+ const VALID_TYPES = ['button', 'submit', 'reset'];
10
+
4
11
  /**
5
12
  *
6
13
  * @element cfpb-button
@@ -12,12 +19,23 @@ export class CfpbButton extends LitElement {
12
19
  `;
13
20
 
14
21
  /**
22
+ * @property {string} href - The URL to link to (makes the button a link).
15
23
  * @property {boolean} disabled - Whether to stack the tags vertically.
16
- * @property {string} type - The button type: secondary, warning, disabled.
24
+ * @property {string} variant - The button variant: secondary and warning.
25
+ * @property {boolean} fullOnMobile - Whether to be width 100% on mobile.
26
+ * @property {string} type - The button type: button, submit, or reset.
27
+ * @returns {object} The map of properties.
17
28
  */
18
29
  static get properties() {
19
30
  return {
20
- disabled: { type: Boolean },
31
+ href: { type: String },
32
+ disabled: { type: Boolean, reflect: true },
33
+ variant: { type: String },
34
+ fullOnMobile: {
35
+ type: Boolean,
36
+ attribute: 'full-on-mobile',
37
+ reflect: true,
38
+ },
21
39
  type: { type: String },
22
40
  };
23
41
  }
@@ -25,29 +43,94 @@ export class CfpbButton extends LitElement {
25
43
  constructor() {
26
44
  super();
27
45
  this.disabled = false;
28
- this.type = '';
29
- }
30
-
31
- get _btnClass() {
32
- let btnClass = 'a-btn';
33
- switch (this.type) {
34
- case 'secondary':
35
- btnClass += ' a-btn--secondary';
36
- break;
37
- case 'warning':
38
- btnClass += ' a-btn--warning';
39
- break;
40
- case 'disabled':
41
- btnClass += ' a-btn--disabled';
42
- break;
46
+ this.variant = 'primary';
47
+ this.fullOnMobile = false;
48
+ this.type = 'button';
49
+ }
50
+
51
+ /**
52
+ * Hide any icon in the slot.
53
+ */
54
+ hideIcon() {
55
+ const icon = this.#findIconInSlot();
56
+ if (icon) icon.style.display = 'none';
57
+ }
58
+
59
+ /**
60
+ * Show any icon in the slot, if it was hidden.
61
+ */
62
+ showIcon() {
63
+ const icon = this.#findIconInSlot();
64
+ if (icon) icon.style.display = '';
65
+ }
66
+
67
+ /**
68
+ * Find the icon SVG in the slot.
69
+ * @returns {Node} The icon SVG node.
70
+ */
71
+ #findIconInSlot() {
72
+ const slot = this.shadowRoot.querySelector('slot');
73
+ const nodes = slot.assignedNodes({ flatten: true });
74
+
75
+ for (const node of nodes) {
76
+ if (node.tagName.toLowerCase() === 'svg') {
77
+ return node;
78
+ }
43
79
  }
80
+ }
44
81
 
45
- return btnClass;
82
+ /**
83
+ * Ensure the variant value is valid, and fall back to a default if not.
84
+ * @returns {string} A valid variant value string.
85
+ */
86
+ get #validVariant() {
87
+ return VALID_VARIANTS.includes(this.variant) ? this.variant : 'primary';
88
+ }
89
+
90
+ /**
91
+ * Ensure the type value is valid, and fall back to a default if not.
92
+ * @returns {string} A valid type value string.
93
+ */
94
+ get #validType() {
95
+ return VALID_TYPES.includes(this.type) ? this.type : 'button';
96
+ }
97
+
98
+ /**
99
+ * The classes added to the button.
100
+ * @returns {object} A classmap of CSS class names.
101
+ */
102
+ get #btnClass() {
103
+ return {
104
+ 'a-btn': true,
105
+ [`a-btn--${this.#validVariant}`]: this.#validVariant !== 'primary',
106
+ };
46
107
  }
47
108
 
48
109
  render() {
110
+ const classes = classMap(this.#btnClass);
111
+
112
+ // Link button form.
113
+ if (this.href) {
114
+ return html`
115
+ <a
116
+ class=${classes}
117
+ href=${this.disabled ? undefined : this.href}
118
+ role="button"
119
+ aria-disabled=${String(this.disabled)}
120
+ tabindex=${this.disabled ? -1 : 0}
121
+ >
122
+ <slot></slot
123
+ ></a>
124
+ `;
125
+ }
126
+
127
+ // Button form.
49
128
  return html`
50
- <button class="${this._btnClass}" ?disabled=${this.disabled}>
129
+ <button
130
+ class=${classes}
131
+ ?disabled=${this.disabled}
132
+ type=${this.#validType}
133
+ >
51
134
  <slot></slot>
52
135
  </button>
53
136
  `;
@@ -15,7 +15,11 @@ export class CfpbFileUpload extends LitElement {
15
15
 
16
16
  static get properties() {
17
17
  return {
18
- isDetailHidden: { type: Boolean },
18
+ isDetailHidden: {
19
+ type: Boolean,
20
+ attribute: 'hidden', // Maps 'hidden' to 'isDetailHidden' property.
21
+ reflect: true, // Reflects the property change back to the attribute.
22
+ },
19
23
  fileName: { type: String }, // The file name.
20
24
  accept: { type: String }, // The accepted file types.
21
25
  value: { type: String }, // The raw file name.
@@ -66,7 +70,7 @@ export class CfpbFileUpload extends LitElement {
66
70
  render() {
67
71
  return html`
68
72
  <cfpb-button
69
- type="secondary"
73
+ variant="secondary"
70
74
  @click="${() => {
71
75
  this.fileInput.value.click();
72
76
  }}"
@@ -17,7 +17,12 @@
17
17
  // Private variables.
18
18
  --choice-border-width-addendum: 0;
19
19
 
20
- .a-text-input--full {
20
+ &--in-list label {
21
+ box-sizing: border-box;
22
+ padding-top: math.div(5px, $base-font-size-px) + em;
23
+ padding-right: 0;
24
+ padding-bottom: math.div(5px, $base-font-size-px) + em;
25
+ padding-left: math.div(10px, $base-font-size-px) + em;
21
26
  width: 100%;
22
27
  }
23
28
 
@@ -1,8 +1,14 @@
1
1
  import { html, LitElement, css, unsafeCSS } from 'lit';
2
+ import { classMap } from 'lit/directives/class-map.js';
2
3
  import styles from './cfpb-form-choice.component.scss';
3
4
 
5
+ // The validation states are error, warning, or success.
6
+ const VALID_VALIDATION = ['error', 'warning', 'success'];
7
+
8
+ // The types are a checkbox or radio button.
9
+ const VALID_TYPES = ['checkbox', 'radio'];
10
+
4
11
  /**
5
- *
6
12
  * @element cfpb-form-choice
7
13
  * @slot - The label for the form input.
8
14
  */
@@ -17,6 +23,10 @@ export class CfpbFormChoice extends LitElement {
17
23
  * @property {boolean} large - Whether the choice has a large target area.
18
24
  * @property {string} validation - Validation style: error, warning, success.
19
25
  * @property {string} type - Choice type: checkbox or radio.
26
+ * @property {string} inlist - Whether the choice appears in a <li> list.
27
+ * @property {string} name - The name within a form.
28
+ * @property {string} value - The value to submit within a form.
29
+ * @returns {object} The map of properties.
20
30
  */
21
31
  static get properties() {
22
32
  return {
@@ -25,41 +35,25 @@ export class CfpbFormChoice extends LitElement {
25
35
  large: { type: Boolean },
26
36
  validation: { type: String },
27
37
  type: { type: String },
38
+ inlist: { type: Boolean, attribute: true },
39
+ name: { type: String },
40
+ value: { type: String },
28
41
  };
29
42
  }
30
43
 
31
44
  constructor() {
32
45
  super();
46
+ this.checked = false;
33
47
  this.disabled = false;
34
48
  this.large = false;
35
49
  this.validation = '';
36
50
  this.type = 'checkbox';
51
+ this.inlist = false;
52
+ this.name = '';
53
+ this.value = '';
37
54
  }
38
55
 
39
- get #baseClass() {
40
- let baseClass = `m-form-field m-form-field--${this.type}`;
41
-
42
- switch (this.validation) {
43
- case 'success':
44
- baseClass += ` m-form-field--${this.type}-success`;
45
- break;
46
- case 'warning':
47
- baseClass += ` m-form-field--${this.type}-warning`;
48
- break;
49
- case 'error':
50
- baseClass += ` m-form-field--${this.type}-error`;
51
- break;
52
- }
53
-
54
- if (this.large) {
55
- baseClass += ' m-form-field--lg-target';
56
- }
57
-
58
- return baseClass;
59
- }
60
-
61
- #onChange(evt) {
62
- evt.target.checked = this.checked;
56
+ #onChange() {
63
57
  this.dispatchEvent(
64
58
  new Event('change', {
65
59
  bubbles: true,
@@ -77,19 +71,58 @@ export class CfpbFormChoice extends LitElement {
77
71
  );
78
72
  }
79
73
 
74
+ focus() {
75
+ this.shadowRoot.querySelector('input').focus();
76
+ }
77
+
78
+ /**
79
+ * Ensure the validation value is valid, and fall back to a default if not.
80
+ * @returns {string|undefined} A valid validation value string, or undefined.
81
+ */
82
+ get #validValidation() {
83
+ return VALID_VALIDATION.includes(this.validation)
84
+ ? this.validation
85
+ : undefined;
86
+ }
87
+
88
+ /**
89
+ * Ensure the type value is valid, and fall back to a default if not.
90
+ * @returns {string} A type value string.
91
+ */
92
+ get #validType() {
93
+ return VALID_TYPES.includes(this.type) ? this.type : 'checkbox';
94
+ }
95
+
96
+ get #baseClass() {
97
+ const classes = {
98
+ 'm-form-field': true,
99
+ [`m-form-field--${this.type}`]: true,
100
+ 'm-form-field--lg-target': this.large,
101
+ 'm-form-field--in-list': this.inlist,
102
+ };
103
+
104
+ if (this.#validValidation)
105
+ classes[[`m-form-field--${this.type}-${this.validation}`]] =
106
+ this.validation;
107
+ return classes;
108
+ }
109
+
80
110
  render() {
111
+ const classes = classMap(this.#baseClass);
112
+
81
113
  return html`
82
- <div class="${this.#baseClass}" ?large=${this.large}>
114
+ <div class="${classes}" ?large=${this.large}>
83
115
  <input
84
116
  class="a-${this.type}"
85
- type="${this.type}"
86
- id="${this.type}"
117
+ type="${this.#validType}"
118
+ id="choice-input"
87
119
  ?disabled=${this.disabled}
88
120
  .checked=${this.checked}
89
121
  @change=${this.#onChange}
90
122
  @input=${this.#onInput}
123
+ aria-invalid=${this.#validValidation === 'error' ? 'true' : 'false'}
91
124
  />
92
- <label class="a-label" for="${this.type}">
125
+ <label class="a-label" for="choice-input">
93
126
  <slot></slot>
94
127
  </label>
95
128
  </div>
@@ -0,0 +1,47 @@
1
+ import { jest } from '@jest/globals';
2
+ import { CfpbFormChoice } from './index.js';
3
+
4
+ describe('<cfpb-form-choice>', () => {
5
+ let elm;
6
+
7
+ beforeEach(async () => {
8
+ CfpbFormChoice.init();
9
+ elm = document.createElement('cfpb-form-choice');
10
+ document.body.appendChild(elm);
11
+
12
+ await customElements.whenDefined('cfpb-form-choice');
13
+ await elm.updateComplete;
14
+ });
15
+
16
+ afterEach(() => {
17
+ document.body.removeChild(elm);
18
+ });
19
+
20
+ it('renders slotted content', async () => {
21
+ const slottedContent = document.createElement('span');
22
+ slottedContent.textContent = 'Earth';
23
+ elm.appendChild(slottedContent);
24
+ await elm.updateComplete;
25
+
26
+ const slot = elm.shadowRoot.querySelector('slot');
27
+ const assignedNodes = slot.assignedNodes({ flatten: true });
28
+
29
+ expect(assignedNodes.length).toBe(1);
30
+ expect(assignedNodes[0].textContent).toBe('Earth');
31
+ });
32
+
33
+ it('dispatches the correct event', async () => {
34
+ const inputMockHandler = jest.fn();
35
+ const changeMockHandler = jest.fn();
36
+ elm.addEventListener('input', inputMockHandler);
37
+ elm.addEventListener('change', changeMockHandler);
38
+
39
+ elm.shadowRoot.querySelector('label').click();
40
+
41
+ expect(inputMockHandler).toHaveBeenCalledTimes(1);
42
+ expect(inputMockHandler.mock.calls[0][0].target).toBe(elm);
43
+
44
+ expect(changeMockHandler).toHaveBeenCalledTimes(1);
45
+ expect(changeMockHandler.mock.calls[0][0].target).toBe(elm);
46
+ });
47
+ });
@@ -0,0 +1,36 @@
1
+ @use 'sass:math';
2
+ @use '@cfpb/cfpb-design-system/src/abstracts' as *;
3
+
4
+ :host {
5
+ .a-label {
6
+ slot {
7
+ display: inline-block;
8
+ }
9
+
10
+ &__helper {
11
+ color: $label-helper;
12
+ font-size: math.div(16px, $base-font-size-px) + rem;
13
+ font-weight: normal;
14
+
15
+ &--block {
16
+ display: block;
17
+
18
+ // Add a gap between the label helper and label.
19
+ margin-top: math.div(10px, $size-vi) + em;
20
+ }
21
+ }
22
+
23
+ &--heading {
24
+ display: block;
25
+
26
+ margin-bottom: math.div(10px, $size-iv) + em;
27
+
28
+ @include heading-4($has-margin-bottom: false);
29
+
30
+ // Add a gap between the label helper and label heading
31
+ .a-label__helper--block {
32
+ margin-top: math.div(10px, $base-font-size-px) + rem;
33
+ }
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,61 @@
1
+ import { html, LitElement, css, unsafeCSS } from 'lit';
2
+ import styles from './cfpb-label.component.scss';
3
+ import { ifDefined } from 'lit/directives/if-defined.js';
4
+
5
+ /**
6
+ *
7
+ * @element cfpb-multiselect.
8
+ * @slot - The main content for the upload button.
9
+ */
10
+ export class CfpbLabel extends LitElement {
11
+ static styles = css`
12
+ ${unsafeCSS(styles)}
13
+ `;
14
+
15
+ /**
16
+ * @property {string} for - Associate the label with an ID elsewhere.
17
+ * @property {string} type - Associate the label with an ID elsewhere.
18
+ * @returns {object} The map of properties.
19
+ */
20
+ static get properties() {
21
+ return {
22
+ // Other properties.
23
+ block: { type: Boolean, reflect: true },
24
+ for: { type: String },
25
+ };
26
+ }
27
+
28
+ constructor() {
29
+ super();
30
+ this.block = false;
31
+ this.for = '';
32
+ }
33
+
34
+ get #helperClass() {
35
+ let helperClass = 'a-label__helper';
36
+ if (this.block) {
37
+ helperClass += ' a-label__helper--block';
38
+ }
39
+
40
+ return helperClass;
41
+ }
42
+
43
+ render() {
44
+ return html`
45
+ <label
46
+ class="a-label a-label--heading"
47
+ for=${ifDefined(this.for && this.for.trim() ? this.for : undefined)}
48
+ >
49
+ <slot name="label"></slot>
50
+ <small class="${this.#helperClass}">
51
+ <slot name="helper"></slot>
52
+ </small>
53
+ </label>
54
+ `;
55
+ }
56
+
57
+ static init() {
58
+ window.customElements.get('cfpb-label') ||
59
+ window.customElements.define('cfpb-label', CfpbLabel);
60
+ }
61
+ }
@@ -0,0 +1,225 @@
1
+ @use 'sass:math';
2
+ @use '@cfpb/cfpb-design-system/src/base' as *;
3
+ @use '@cfpb/cfpb-design-system/src/abstracts' as *;
4
+ @use '@cfpb/cfpb-design-system/src/components/cfpb-buttons/vars' as *;
5
+
6
+ :host {
7
+ // Theme variables.
8
+ --select-input-border: var(--gray-60);
9
+ --select-input-border-hover: var(--pacific);
10
+ --select-input-border-focus: var(--pacific);
11
+ --select-input-bg: var(--white);
12
+ --select-input-text: var(--black);
13
+
14
+ // Initial and no-js state.
15
+ select.o-multiselect {
16
+ display: block;
17
+ box-sizing: border-box;
18
+ width: 100%;
19
+ padding: math.div(7px, $base-font-size-px) + em;
20
+
21
+ // Fixed height breaks the bottom border
22
+ // mid-character to indicate there's more content.
23
+ height: 5.5em;
24
+ padding-top: math.div(4px, $base-font-size-px) + em;
25
+ padding-bottom: math.div(4px, $base-font-size-px) + em;
26
+ border: 1px solid var(--select-border-default);
27
+
28
+ option {
29
+ padding: math.div(2px, $base-font-size-px) + em
30
+ math.div(6px, $base-font-size-px) + em;
31
+ }
32
+ }
33
+
34
+ .o-multiselect {
35
+ position: relative;
36
+
37
+ & header {
38
+ position: relative;
39
+
40
+ &::after {
41
+ // Arrow box width must be odd size to properly center the bg image
42
+ width: math.div($select-height, $base-font-size-px) + em;
43
+ box-sizing: border-box;
44
+ border: 1px solid var(--select-border-default);
45
+ position: absolute;
46
+ top: 0;
47
+ right: 0;
48
+ bottom: 0;
49
+ background-color: var(--select-icon-bg-default);
50
+
51
+ --cfpb-background-icon-svg: 'down';
52
+
53
+ background-size: auto $cf-icon-height;
54
+ background-repeat: no-repeat;
55
+ background-position: center center;
56
+ content: '';
57
+ pointer-events: none;
58
+ }
59
+ }
60
+
61
+ & input[type='text'] {
62
+ width: 100%;
63
+ min-height: 35px;
64
+
65
+ // Reset the browser's default styling.
66
+ appearance: none;
67
+ display: inline-block;
68
+ padding: math.div(7px, $base-font-size-px) + em;
69
+ border: 1px solid var(--select-input-border);
70
+ outline: 0 solid var(--select-input-border);
71
+ background: var(--select-input-bg);
72
+ color: var(--select-input-text);
73
+ box-sizing: border-box;
74
+
75
+ &:hover,
76
+ &.hover {
77
+ border-color: var(--select-input-border-hover);
78
+ outline: 1px solid var(--select-input-border-hover);
79
+ }
80
+
81
+ &:focus,
82
+ &.focus {
83
+ border-color: var(--select-input-border-focus);
84
+ box-shadow: 0 0 0 1px var(--select-input-border-focus);
85
+ outline: 1px dotted var(--select-input-border-focus);
86
+ outline-offset: 2px;
87
+ }
88
+ }
89
+
90
+ & fieldset {
91
+ // Resets
92
+ border-color: var(--select-border-default);
93
+ border-top: none;
94
+ margin: 0;
95
+ padding: 0;
96
+
97
+ // Styles
98
+ box-sizing: border-box;
99
+ overflow-x: hidden;
100
+ overflow-y: scroll;
101
+ position: absolute;
102
+ z-index: 10;
103
+
104
+ max-height: 0;
105
+ margin-top: -1px;
106
+ width: 100%;
107
+
108
+ transition: max-height 0.25s ease-out;
109
+ }
110
+
111
+ &.u-active {
112
+ fieldset {
113
+ margin-top: 0;
114
+
115
+ // This needs to match the value set in _bindEvents in Multiselect.js.
116
+ // See https://github.com/cfpb/design-system/blob/4d26d5af04317bcc00b4677aa866fe8d526e82e0/packages/cfpb-forms/src/organisms/Multiselect.js#L340
117
+ max-height: 140px;
118
+
119
+ border-color: var(--pacific);
120
+ border-width: 2px;
121
+ border-top: 0;
122
+ }
123
+
124
+ // Reverse arrow when search drop-down is open.
125
+ header::after {
126
+ --cfpb-background-icon-svg: 'up';
127
+ }
128
+ }
129
+
130
+ & ul {
131
+ list-style-type: none;
132
+ background-color: var(--white);
133
+ padding: 0;
134
+ padding-top: math.div(5px, $base-font-size-px) + em;
135
+ padding-bottom: math.div(5px, $base-font-size-px) + em;
136
+
137
+ li {
138
+ margin: 0;
139
+ }
140
+
141
+ li:first-child {
142
+ .a-label {
143
+ padding-top: math.div(10px, $base-font-size-px) + em;
144
+ }
145
+ }
146
+
147
+ &.u-filtered li:not(.u-filter-match) {
148
+ display: none;
149
+ }
150
+
151
+ &.u-no-results,
152
+ &.u-max-selections {
153
+ padding: math.div(10px, $base-font-size-px) + em;
154
+ li {
155
+ display: none;
156
+ }
157
+
158
+ &::after {
159
+ display: list-item;
160
+ }
161
+ }
162
+
163
+ &.u-no-results::after {
164
+ content: 'No results found';
165
+ }
166
+
167
+ &.u-max-selections {
168
+ pointer-events: none;
169
+
170
+ &::after {
171
+ content: 'Reached maximum number of selections';
172
+ }
173
+ }
174
+ }
175
+ }
176
+
177
+ .u-invisible {
178
+ visibility: hidden;
179
+ }
180
+
181
+ /* button {
182
+ // Filter tags appear in filtered contexts, often as part of multiselects.
183
+ line-height: math.div(19px, $base-font-size-px);
184
+
185
+ display: flex;
186
+ gap: math.div(10px, $btn-font-size) + rem;
187
+
188
+ border: 1px solid var(--teal);
189
+ padding: 4px 6px;
190
+ background-color: var(--teal-20);
191
+ border-radius: math.div(3px, $base-font-size-px) + rem;
192
+ color: var(--black);
193
+ text-align: left;
194
+ min-width: fit-content;
195
+
196
+ &:hover {
197
+ background-color: var(--teal-40);
198
+ cursor: pointer;
199
+ }
200
+
201
+ &:focus {
202
+ outline: 1px dotted var(--teal);
203
+ outline-offset: 1px;
204
+ }
205
+
206
+ &:active {
207
+ background-color: var(--teal-60);
208
+ }
209
+ }
210
+
211
+ svg {
212
+ pointer-events: none;
213
+
214
+ // Prevent flexbox from squishing icon when tag text is long.
215
+ flex: none;
216
+
217
+ height: 1rem;
218
+ }
219
+
220
+ // If the contents are wrapped in a label, negate the label's display.
221
+ label {
222
+ display: contents;
223
+ pointer-events: none;
224
+ } */
225
+ }