@cfpb/cfpb-design-system 4.0.1 → 4.0.3
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.
- package/CHANGELOG.md +43 -1
- package/dist/components/cfpb-forms/index.css +1 -1
- package/dist/components/cfpb-forms/index.css.map +2 -2
- package/dist/components/cfpb-forms/index.js.map +1 -1
- package/dist/components/cfpb-layout/index.css +1 -1
- package/dist/components/cfpb-layout/index.css.map +2 -2
- package/dist/components/cfpb-layout/index.js.map +1 -1
- package/dist/components/cfpb-typography/index.css +1 -1
- package/dist/components/cfpb-typography/index.css.map +2 -2
- package/dist/components/cfpb-typography/index.js.map +1 -1
- package/dist/elements/cfpb-button/index.js +2 -2
- package/dist/elements/cfpb-button/index.js.map +1 -1
- package/dist/elements/cfpb-checkbox/index.js +29 -0
- package/dist/elements/cfpb-checkbox/index.js.map +7 -0
- package/dist/elements/cfpb-file-upload/index.js +2 -2
- package/dist/elements/cfpb-file-upload/index.js.map +1 -1
- package/dist/elements/cfpb-form-choice/index.js +29 -0
- package/dist/elements/cfpb-form-choice/index.js.map +7 -0
- package/dist/elements/cfpb-multiselect/index.js +30 -0
- package/dist/elements/cfpb-multiselect/index.js.map +7 -0
- package/dist/elements/cfpb-tag-filter/index.js +31 -0
- package/dist/elements/cfpb-tag-filter/index.js.map +7 -0
- package/dist/elements/cfpb-tag-group/index.js +29 -0
- package/dist/elements/cfpb-tag-group/index.js.map +7 -0
- package/dist/elements/cfpb-tag-topic/index.js +30 -0
- package/dist/elements/cfpb-tag-topic/index.js.map +7 -0
- package/dist/elements/index.js +42 -0
- package/dist/elements/index.js.map +7 -0
- package/dist/index.css +1 -1
- package/dist/index.css.map +2 -2
- package/dist/index.js +6 -4
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/src/components/cfpb-forms/multiselect-model.js +1 -1
- package/src/components/cfpb-forms/multiselect.js +1 -1
- package/src/components/cfpb-forms/multiselect.scss +2 -1
- package/src/components/cfpb-layout/card-group.scss +2 -2
- package/src/components/cfpb-typography/mixins.scss +1 -1
- package/src/components/cfpb-typography/tagline.scss +1 -1
- package/src/elements/cfpb-button/index.js +7 -3
- package/src/elements/cfpb-form-choice/cfpb-form-choice.component.scss +261 -0
- package/src/elements/cfpb-form-choice/index.js +103 -0
- package/src/elements/cfpb-tag-filter/cfpb-tag-filter.component.scss +51 -0
- package/src/elements/cfpb-tag-filter/index.js +55 -0
- package/src/elements/cfpb-tag-filter/index.spec.js +43 -0
- package/src/elements/cfpb-tag-group/cfpb-tag-group.component.scss +43 -0
- package/src/elements/cfpb-tag-group/index.js +311 -0
- package/src/elements/cfpb-tag-group/index.spec.js +160 -0
- package/src/elements/cfpb-tag-topic/cfpb-tag-topic.component.scss +88 -0
- package/src/elements/cfpb-tag-topic/index.js +62 -0
- package/src/elements/index.js +11 -0
- package/src/index.js +1 -2
- package/src/utilities/utilities.scss +1 -1
- package/src/components/cfpb-icons/font/CFPBIcons-Regular.otf +0 -0
package/package.json
CHANGED
|
@@ -26,7 +26,7 @@ function stringMatch(x, y) {
|
|
|
26
26
|
/**
|
|
27
27
|
* @class
|
|
28
28
|
* MultiselectModel
|
|
29
|
-
* @param {HTMLOptionsCollection} options
|
|
29
|
+
* @param {HTMLOptionsCollection} options
|
|
30
30
|
* Set of options from a <select> element.
|
|
31
31
|
* @param {string} name - a unique name for this multiselect.
|
|
32
32
|
* @param {object} config - Customization of Multiselect behavior
|
|
@@ -172,7 +172,7 @@ function Multiselect(element) {
|
|
|
172
172
|
|
|
173
173
|
/**
|
|
174
174
|
* Highlights an option in the list.
|
|
175
|
-
* @param {string} direction
|
|
175
|
+
* @param {string} direction
|
|
176
176
|
* Direction to highlight compared to the current focus.
|
|
177
177
|
*/
|
|
178
178
|
function _highlight(direction) {
|
|
@@ -116,6 +116,8 @@ select.o-multiselect {
|
|
|
116
116
|
|
|
117
117
|
&.u-no-results,
|
|
118
118
|
&.u-max-selections {
|
|
119
|
+
padding: math.div(10px, $base-font-size-px) + em;
|
|
120
|
+
|
|
119
121
|
li {
|
|
120
122
|
display: none;
|
|
121
123
|
}
|
|
@@ -130,7 +132,6 @@ select.o-multiselect {
|
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
&.u-max-selections {
|
|
133
|
-
padding: math.div(10px, $base-font-size-px) + em;
|
|
134
135
|
pointer-events: none;
|
|
135
136
|
|
|
136
137
|
&::after {
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
|
|
23
23
|
&__cards {
|
|
24
24
|
display: grid;
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
column-gap: math.div(20px, $base-font-size-px) + em;
|
|
26
|
+
row-gap: math.div(20px, $base-font-size-px) + em;
|
|
27
27
|
|
|
28
28
|
// Mobile only.
|
|
29
29
|
@include respond-to-max($bp-xs-max) {
|
|
@@ -11,16 +11,20 @@ export class CfpbButton extends LitElement {
|
|
|
11
11
|
${unsafeCSS(styles)}
|
|
12
12
|
`;
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @property {boolean} disabled - Whether to stack the tags vertically.
|
|
16
|
+
* @property {string} type - The button type: secondary, warning, disabled.
|
|
17
|
+
*/
|
|
14
18
|
static get properties() {
|
|
15
19
|
return {
|
|
16
|
-
|
|
20
|
+
disabled: { type: Boolean },
|
|
17
21
|
type: { type: String },
|
|
18
22
|
};
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
constructor() {
|
|
22
26
|
super();
|
|
23
|
-
this.
|
|
27
|
+
this.disabled = false;
|
|
24
28
|
this.type = '';
|
|
25
29
|
}
|
|
26
30
|
|
|
@@ -43,7 +47,7 @@ export class CfpbButton extends LitElement {
|
|
|
43
47
|
|
|
44
48
|
render() {
|
|
45
49
|
return html`
|
|
46
|
-
<button class="${this._btnClass}" ?disabled=${this.
|
|
50
|
+
<button class="${this._btnClass}" ?disabled=${this.disabled}>
|
|
47
51
|
<slot></slot>
|
|
48
52
|
</button>
|
|
49
53
|
`;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
@use 'sass:math';
|
|
2
|
+
@use '@cfpb/cfpb-design-system/src/abstracts' as *;
|
|
3
|
+
@use '@cfpb/cfpb-design-system/src/utilities' as *;
|
|
4
|
+
|
|
5
|
+
:host {
|
|
6
|
+
.m-form-field {
|
|
7
|
+
// Theme variables.
|
|
8
|
+
--choice-border: var(--choice-border-default);
|
|
9
|
+
--choice-border-hover: var(--choice-border-hover-default);
|
|
10
|
+
--choice-border-focus: var(--choice-border-focus-default);
|
|
11
|
+
--choice-outline-focus: var(--choice-outline-focus-default);
|
|
12
|
+
--choice-bg: var(--choice-bg-default);
|
|
13
|
+
--choice-bg-selected: var(--choice-bg-selected-default);
|
|
14
|
+
--choice-bg-selected-focus: var(--choice-bg-selected-focus-default);
|
|
15
|
+
--choice-label-disabled: var(--choice-label-disabled-default);
|
|
16
|
+
|
|
17
|
+
// Private variables.
|
|
18
|
+
--choice-border-width-addendum: 0;
|
|
19
|
+
|
|
20
|
+
.a-text-input--full {
|
|
21
|
+
width: 100%;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.a-label + .a-text-input {
|
|
25
|
+
margin-top: math.div(5px, $base-font-size-px) + em;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
&--checkbox,
|
|
29
|
+
&--radio {
|
|
30
|
+
.a-label {
|
|
31
|
+
display: inline-grid;
|
|
32
|
+
|
|
33
|
+
// 30px is width of checkbox/radio button plus the needed padding.
|
|
34
|
+
grid-template-columns: math.div(30px, $base-font-size-px) + em auto;
|
|
35
|
+
vertical-align: top;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
|
|
38
|
+
// Wrap long words in narrow form fields to prevent clipping
|
|
39
|
+
overflow-wrap: anywhere;
|
|
40
|
+
|
|
41
|
+
&::before {
|
|
42
|
+
display: inline-block;
|
|
43
|
+
grid-row-start: 1;
|
|
44
|
+
grid-row-end: 3;
|
|
45
|
+
border: 1px solid var(--choice-border);
|
|
46
|
+
outline: var(--choice-border-width-addendum) solid
|
|
47
|
+
var(--choice-border);
|
|
48
|
+
height: math.div(18px, $base-font-size-px) + em;
|
|
49
|
+
width: math.div(18px, $base-font-size-px) + em;
|
|
50
|
+
margin-right: 10px;
|
|
51
|
+
background-color: var(--choice-bg);
|
|
52
|
+
content: '';
|
|
53
|
+
vertical-align: top;
|
|
54
|
+
|
|
55
|
+
// Offset so that the checkbox/radio fits within focused area.
|
|
56
|
+
position: relative;
|
|
57
|
+
top: 1px;
|
|
58
|
+
left: 1px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&:hover::before,
|
|
62
|
+
&.hover::before {
|
|
63
|
+
border-color: var(--choice-border-hover);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.a-checkbox,
|
|
68
|
+
.a-radio {
|
|
69
|
+
@include u-visually-hidden;
|
|
70
|
+
|
|
71
|
+
&:focus + .a-label,
|
|
72
|
+
&.focus + .a-label {
|
|
73
|
+
outline: 1px dotted var(--choice-outline-focus);
|
|
74
|
+
outline-offset: 1px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
&:disabled {
|
|
78
|
+
&:checked + .a-label::before,
|
|
79
|
+
&:focus + .a-label::before,
|
|
80
|
+
&.focus + .a-label::before,
|
|
81
|
+
&:hover + .a-label::before,
|
|
82
|
+
&.hover + .a-label::before {
|
|
83
|
+
border-color: var(--choice-border);
|
|
84
|
+
outline: none;
|
|
85
|
+
box-shadow: none; // Applies only to radio buttons.
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
& + .a-label {
|
|
89
|
+
cursor: not-allowed;
|
|
90
|
+
color: var(--choice-label-disabled);
|
|
91
|
+
|
|
92
|
+
&::before {
|
|
93
|
+
outline: none;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
&:has(.a-checkbox:disabled),
|
|
100
|
+
&:has(.a-radio:disabled) {
|
|
101
|
+
--choice-border: var(--choice-border-disabled);
|
|
102
|
+
--choice-bg: var(--choice-bg-disabled);
|
|
103
|
+
--choice-bg-selected: var(--choice-bg-selected-disabled);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
&-success,
|
|
107
|
+
&-warning,
|
|
108
|
+
&-error {
|
|
109
|
+
--choice-border-width-addendum: 1px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
&-success {
|
|
113
|
+
--choice-border: var(--choice-border-success);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
&-warning {
|
|
117
|
+
--choice-border: var(--choice-border-warning);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
&-error {
|
|
121
|
+
--choice-border: var(--choice-border-error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
&--checkbox {
|
|
126
|
+
.a-checkbox {
|
|
127
|
+
&:focus + .a-label::before,
|
|
128
|
+
&.focus + .a-label::before {
|
|
129
|
+
border-color: var(--choice-border-focus);
|
|
130
|
+
box-shadow: 0 0 0 1px var(--choice-border-focus);
|
|
131
|
+
outline-color: var(--choice-border-focus);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
&:hover + .a-label::before,
|
|
135
|
+
&.hover + .a-label::before {
|
|
136
|
+
border-color: var(--choice-border-hover);
|
|
137
|
+
box-shadow: 0 0 0 1px var(--choice-border-hover);
|
|
138
|
+
outline-color: var(--choice-border-hover);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
&:checked + .a-label::before {
|
|
142
|
+
--cfpb-background-icon-svg: 'approved';
|
|
143
|
+
|
|
144
|
+
background-size: auto $cf-icon-height;
|
|
145
|
+
background-repeat: no-repeat;
|
|
146
|
+
background-position: center 0;
|
|
147
|
+
}
|
|
148
|
+
&:disabled:checked + .a-label::before {
|
|
149
|
+
// RGB values are CFPB gray (#5a5d61).
|
|
150
|
+
// For some reason SVG isn't accepting hex values for the fill.
|
|
151
|
+
--cfpb-background-icon-svg: 'approved rgb(90,93,97)';
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
&--radio {
|
|
157
|
+
.a-label {
|
|
158
|
+
&::before {
|
|
159
|
+
border-radius: 50%;
|
|
160
|
+
|
|
161
|
+
/* The rotate is needed to fix a bug in Firefox where radio
|
|
162
|
+
button was not centered. */
|
|
163
|
+
transform: rotate(0deg);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.a-radio {
|
|
168
|
+
&:focus + .a-label::before,
|
|
169
|
+
&.focus + .a-label::before {
|
|
170
|
+
outline: none;
|
|
171
|
+
border-color: var(--choice-border-focus);
|
|
172
|
+
box-shadow: 0 0 0 1px var(--choice-border-focus);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
&:hover + .a-label::before,
|
|
176
|
+
&.hover + .a-label::before {
|
|
177
|
+
outline: none;
|
|
178
|
+
border-color: var(--choice-border-hover);
|
|
179
|
+
box-shadow: 0 0 0 1px var(--choice-border-hover);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
&:checked + .a-label::before {
|
|
183
|
+
background-color: var(--choice-bg-selected);
|
|
184
|
+
box-shadow: inset 0 0 0 2px var(--white); // The radio button ring.
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
&:focus:checked + .a-label::before,
|
|
188
|
+
&.focus:checked + .a-label::before {
|
|
189
|
+
background-color: var(--choice-bg-selected-focus);
|
|
190
|
+
border-color: var(--choice-border-focus);
|
|
191
|
+
box-shadow:
|
|
192
|
+
0 0 0 1px var(--choice-border-focus),
|
|
193
|
+
inset 0 0 0 2px var(--white); // The radio button ring.
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
&:hover:checked + .a-label::before,
|
|
197
|
+
&.hover:checked + .a-label::before {
|
|
198
|
+
border-color: var(--choice-border-hover);
|
|
199
|
+
box-shadow:
|
|
200
|
+
0 0 0 1px var(--choice-border-hover),
|
|
201
|
+
inset 0 0 0 2px var(--white); // The radio button ring.
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
&:checked:disabled + .a-label::before,
|
|
205
|
+
&:hover:checked:disabled + .a-label::before,
|
|
206
|
+
&.hover:checked:disabled + .a-label::before {
|
|
207
|
+
background-color: var(--choice-bg-selected);
|
|
208
|
+
box-shadow: inset 0 0 0 2px var(--gray-10); // The radio button ring.
|
|
209
|
+
border-color: var(--choice-border-disabled);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
&--lg-target {
|
|
215
|
+
display: block;
|
|
216
|
+
|
|
217
|
+
.a-label {
|
|
218
|
+
box-sizing: border-box;
|
|
219
|
+
width: 100%;
|
|
220
|
+
padding: 15px;
|
|
221
|
+
background-color: $form-field-input-lg-target-bg;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.a-checkbox,
|
|
225
|
+
.a-radio {
|
|
226
|
+
&:checked + .a-label {
|
|
227
|
+
background-color: $form-field-input-lg-target-bg-selected;
|
|
228
|
+
box-shadow: inset 0 0 0 1px $form-field-input-lg-target-border;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
&:hover + .a-label,
|
|
232
|
+
&.hover + .a-label,
|
|
233
|
+
&:focus + .a-label,
|
|
234
|
+
&.focus + .a-label {
|
|
235
|
+
box-shadow: inset 0 0 0 2px $form-field-input-lg-target-border;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
&:focus + .a-label,
|
|
239
|
+
&.focus + .a-label,
|
|
240
|
+
&:checked + .a-label {
|
|
241
|
+
outline-offset: 1px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
&:disabled + .a-label,
|
|
245
|
+
&:checked:disabled + .a-label,
|
|
246
|
+
&:hover:disabled + .a-label {
|
|
247
|
+
color: var(--choice-label-disabled);
|
|
248
|
+
box-shadow: none;
|
|
249
|
+
background-color: $form-field-input-lg-target-bg-disabled;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
&:checked:disabled + .a-label {
|
|
253
|
+
&,
|
|
254
|
+
&::before {
|
|
255
|
+
border: 1px solid var(--form-field-border-disabled);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { html, LitElement, css, unsafeCSS } from 'lit';
|
|
2
|
+
import styles from './cfpb-form-choice.component.scss';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @element cfpb-form-choice
|
|
7
|
+
* @slot - The label for the form input.
|
|
8
|
+
*/
|
|
9
|
+
export class CfpbFormChoice extends LitElement {
|
|
10
|
+
static styles = css`
|
|
11
|
+
${unsafeCSS(styles)}
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @property {boolean} checked - Whether the choice is checked or not.
|
|
16
|
+
* @property {boolean} disabled - Whether the choice is disabled or not.
|
|
17
|
+
* @property {boolean} large - Whether the choice has a large target area.
|
|
18
|
+
* @property {string} validation - Validation style: error, warning, success.
|
|
19
|
+
* @property {string} type - Choice type: checkbox or radio.
|
|
20
|
+
*/
|
|
21
|
+
static get properties() {
|
|
22
|
+
return {
|
|
23
|
+
checked: { type: Boolean, reflect: true },
|
|
24
|
+
disabled: { type: Boolean },
|
|
25
|
+
large: { type: Boolean },
|
|
26
|
+
validation: { type: String },
|
|
27
|
+
type: { type: String },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
constructor() {
|
|
32
|
+
super();
|
|
33
|
+
this.disabled = false;
|
|
34
|
+
this.large = false;
|
|
35
|
+
this.validation = '';
|
|
36
|
+
this.type = 'checkbox';
|
|
37
|
+
}
|
|
38
|
+
|
|
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;
|
|
63
|
+
this.dispatchEvent(
|
|
64
|
+
new Event('change', {
|
|
65
|
+
bubbles: true,
|
|
66
|
+
composed: true,
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#onInput() {
|
|
72
|
+
this.dispatchEvent(
|
|
73
|
+
new Event('input', {
|
|
74
|
+
bubbles: true,
|
|
75
|
+
composed: true,
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
render() {
|
|
81
|
+
return html`
|
|
82
|
+
<div class="${this.#baseClass}" ?large=${this.large}>
|
|
83
|
+
<input
|
|
84
|
+
class="a-${this.type}"
|
|
85
|
+
type="${this.type}"
|
|
86
|
+
id="${this.type}"
|
|
87
|
+
?disabled=${this.disabled}
|
|
88
|
+
.checked=${this.checked}
|
|
89
|
+
@change=${this.#onChange}
|
|
90
|
+
@input=${this.#onInput}
|
|
91
|
+
/>
|
|
92
|
+
<label class="a-label" for="${this.type}">
|
|
93
|
+
<slot></slot>
|
|
94
|
+
</label>
|
|
95
|
+
</div>
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static init() {
|
|
100
|
+
window.customElements.get('cfpb-form-choice') ||
|
|
101
|
+
window.customElements.define('cfpb-form-choice', CfpbFormChoice);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
@use 'sass:math';
|
|
2
|
+
@use '@cfpb/cfpb-design-system/src/abstracts' as *;
|
|
3
|
+
@use '@cfpb/cfpb-design-system/src/components/cfpb-buttons/vars' as *;
|
|
4
|
+
|
|
5
|
+
:host {
|
|
6
|
+
button {
|
|
7
|
+
// Filter tags appear in filtered contexts, often as part of multiselects.
|
|
8
|
+
line-height: math.div(19px, $base-font-size-px);
|
|
9
|
+
font-size: math.div(16px, $btn-font-size) + rem;
|
|
10
|
+
|
|
11
|
+
display: flex;
|
|
12
|
+
gap: math.div(10px, $btn-font-size) + rem;
|
|
13
|
+
|
|
14
|
+
border: 1px solid var(--teal);
|
|
15
|
+
padding: 4px 6px;
|
|
16
|
+
background-color: var(--teal-20);
|
|
17
|
+
border-radius: math.div(3px, $base-font-size-px) + rem;
|
|
18
|
+
color: var(--black);
|
|
19
|
+
text-align: left;
|
|
20
|
+
min-width: fit-content;
|
|
21
|
+
|
|
22
|
+
&:hover {
|
|
23
|
+
background-color: var(--teal-40);
|
|
24
|
+
cursor: pointer;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
&:focus {
|
|
28
|
+
outline: 1px dotted var(--teal);
|
|
29
|
+
outline-offset: 1px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&:active {
|
|
33
|
+
background-color: var(--teal-60);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
svg {
|
|
38
|
+
pointer-events: none;
|
|
39
|
+
|
|
40
|
+
// Prevent flexbox from squishing icon when tag text is long.
|
|
41
|
+
flex: none;
|
|
42
|
+
|
|
43
|
+
height: math.div(19px, $btn-font-size) + rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// If the contents are wrapped in a label, negate the label's display.
|
|
47
|
+
label {
|
|
48
|
+
display: contents;
|
|
49
|
+
pointer-events: none;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { html, LitElement, css, unsafeCSS } from 'lit';
|
|
2
|
+
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
3
|
+
import styles from './cfpb-tag-filter.component.scss';
|
|
4
|
+
import icon from '../../components/cfpb-icons/icons/error.svg';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @element cfpb-tag-filter.
|
|
9
|
+
* @slot - The content for the filter tag.
|
|
10
|
+
*/
|
|
11
|
+
export class CfpbTagFilter extends LitElement {
|
|
12
|
+
static styles = css`
|
|
13
|
+
${unsafeCSS(styles)}
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @property {string} for - Whether to stack the tags vertically.
|
|
18
|
+
*/
|
|
19
|
+
static get properties() {
|
|
20
|
+
return {
|
|
21
|
+
for: { type: String },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
constructor() {
|
|
26
|
+
super();
|
|
27
|
+
this.for = '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#onClick() {
|
|
31
|
+
this.dispatchEvent(
|
|
32
|
+
new CustomEvent('tag-click', {
|
|
33
|
+
detail: { target: this },
|
|
34
|
+
bubbles: false,
|
|
35
|
+
composed: false,
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
render() {
|
|
41
|
+
const slot =
|
|
42
|
+
this.for === ''
|
|
43
|
+
? html`<slot></slot>`
|
|
44
|
+
: html`<label for="${this.for}"><slot></slot></label>`;
|
|
45
|
+
return html`<button @click=${this.#onClick}>
|
|
46
|
+
${slot} ${unsafeHTML(icon)}
|
|
47
|
+
</button>`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static init() {
|
|
51
|
+
// Initialize parent file upload.
|
|
52
|
+
window.customElements.get('cfpb-tag-filter') ||
|
|
53
|
+
window.customElements.define('cfpb-tag-filter', CfpbTagFilter);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { CfpbTagFilter } from './index.js';
|
|
3
|
+
|
|
4
|
+
describe('<cfpb-tag-filter>', () => {
|
|
5
|
+
let elm;
|
|
6
|
+
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
CfpbTagFilter.init();
|
|
9
|
+
elm = document.createElement('cfpb-tag-filter');
|
|
10
|
+
document.body.appendChild(elm);
|
|
11
|
+
|
|
12
|
+
await customElements.whenDefined('cfpb-tag-filter');
|
|
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
|
+
elm.setAttribute('for', 'unique-id');
|
|
23
|
+
slottedContent.textContent = 'Earth';
|
|
24
|
+
elm.appendChild(slottedContent);
|
|
25
|
+
await elm.updateComplete;
|
|
26
|
+
|
|
27
|
+
const slot = elm.shadowRoot.querySelector('slot');
|
|
28
|
+
const assignedNodes = slot.assignedNodes({ flatten: true });
|
|
29
|
+
|
|
30
|
+
expect(assignedNodes.length).toBe(1);
|
|
31
|
+
expect(assignedNodes[0].textContent).toBe('Earth');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
xit('dispatches the correct event', async () => {
|
|
35
|
+
const mockHandler = jest.fn();
|
|
36
|
+
elm.addEventListener('tag-click', mockHandler);
|
|
37
|
+
|
|
38
|
+
elm.shadowRoot.querySelector('button').click();
|
|
39
|
+
|
|
40
|
+
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
41
|
+
expect(mockHandler.mock.calls[0][0].detail.target).toBe(elm);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
@use 'sass:math';
|
|
2
|
+
@use '@cfpb/cfpb-design-system/src/abstracts' as *;
|
|
3
|
+
@use '@cfpb/cfpb-design-system/src/components/cfpb-buttons/vars' as *;
|
|
4
|
+
|
|
5
|
+
:host {
|
|
6
|
+
// Tag group sets the spacing between tags.
|
|
7
|
+
ul {
|
|
8
|
+
// Remove list ul bullets styles.
|
|
9
|
+
padding-left: 0;
|
|
10
|
+
list-style-type: none;
|
|
11
|
+
|
|
12
|
+
li {
|
|
13
|
+
margin-bottom: 0;
|
|
14
|
+
display: contents;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Tablet and above.
|
|
18
|
+
@include respond-to-min($bp-sm-min) {
|
|
19
|
+
display: flex;
|
|
20
|
+
gap: math.div(15px, $btn-font-size) + rem;
|
|
21
|
+
flex-wrap: wrap;
|
|
22
|
+
|
|
23
|
+
&[stacked] {
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
width: fit-content;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&:has(cfpb-tag-filter) {
|
|
30
|
+
display: flex;
|
|
31
|
+
gap: math.div(15px, $btn-font-size) + rem;
|
|
32
|
+
flex-wrap: wrap;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Right-to-left (RTL) adjustments for arabic pages.
|
|
37
|
+
html[lang='ar'] {
|
|
38
|
+
direction: rtl;
|
|
39
|
+
|
|
40
|
+
// This is needed for right-to-left (RTL) lists.
|
|
41
|
+
padding-right: 0;
|
|
42
|
+
}
|
|
43
|
+
}
|