kemet-ui 1.1.0 → 1.2.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.
@@ -0,0 +1,209 @@
1
+ import { html, css, LitElement } from 'lit';
2
+
3
+ export default class KemetFAB extends LitElement {
4
+ static get styles() {
5
+ return [
6
+ css`
7
+ :host {
8
+ --kemet-fab-size: 50px;
9
+
10
+ display: inline-block;
11
+ position: relative;
12
+ }
13
+
14
+ button {
15
+ color: var(--kemet-fab-color, var(--kemet-color-white));
16
+ font-size: inherit;
17
+ display: inline-flex;
18
+ padding: 0;
19
+ position: relative;
20
+ min-height: var(--kemet-fab-size);
21
+ min-width: var(--kemet-fab-size);
22
+ max-width: var(--kemet-fab-size);
23
+ align-items: center;
24
+ justify-content: flex-start;
25
+ transition: all 0.4s ease;
26
+ border: none;
27
+ background-color: var(--kemet-fab-background-color, var(--kemet-color-primary));
28
+ }
29
+
30
+ :host([outlined]) button {
31
+ color: var(--kemet-fab-outlined-color, var(--kemet-color-primary));
32
+ border: var(--kemet-fab-outlined-border, 1px solid var(--kemet-color-primary));
33
+ background-color: transparent;
34
+ }
35
+
36
+ :host([pill]) button {
37
+ border-radius: var(--kemet-fab-pill-radius, 10rem);
38
+ }
39
+
40
+ button::before {
41
+ content: '';
42
+ position: absolute;
43
+ top: 0;
44
+ left: 0;
45
+ right: 0;
46
+ bottom: 0;
47
+ z-index: -1;
48
+ width: 100%;
49
+ height: 100%;
50
+ transition: background-color 0.3s ease-in-out;
51
+ background-color: var(--kemet-fab-background-color, var(--kemet-color-primary));
52
+ }
53
+
54
+ :host([pill]) button::before {
55
+ border-radius: var(--kemet-fab-pill-radius, 10rem);
56
+ }
57
+
58
+ :host([outlined]) button::before {
59
+ border: var(--kemet-fab-outline-border, 1px solid var(--kemet-color-primary));
60
+ background-color: transparent;
61
+ }
62
+
63
+ :host([expanded]) button {
64
+ max-width: 99rem;
65
+ padding: 0 1.35rem 0 0.25rem;
66
+ }
67
+
68
+ :host([disabled]) button {
69
+ opacity: 0.5;
70
+ cursor: not-allowed;
71
+ }
72
+
73
+ .icon {
74
+ width: var(--kemet-fab-size);
75
+ height: var(--kemet-fab-size);
76
+ display: flex;
77
+ flex: 0 0 auto;
78
+ align-items: center;
79
+ justify-content: center;
80
+ }
81
+
82
+ .text {
83
+ color: var(--kemet-fab-color, var(--kemet-color-white));
84
+ z-index: 1;
85
+ white-space: nowrap;
86
+ pointer-events: none;
87
+ opacity: 0;
88
+ transition: opacity 0.2s ease-in-out;
89
+ }
90
+
91
+ :host([outlined]) .text {
92
+ color: var(--kemet-fab-outlined-color, var(--kemet-color-primary));
93
+ }
94
+
95
+ :host([expanded]) .text {
96
+ opacity: 1;
97
+ }
98
+ `,
99
+ ];
100
+ }
101
+
102
+ static get properties() {
103
+ return {
104
+ /**
105
+ * The expanded state of the button
106
+ */
107
+ expanded: {
108
+ type: Boolean,
109
+ reflect: true,
110
+ },
111
+ /**
112
+ * Outline style for a button
113
+ */
114
+ outlined: {
115
+ type: Boolean,
116
+ reflect: true,
117
+ },
118
+ /**
119
+ * Determines whether not a button is disabled
120
+ */
121
+ disabled: {
122
+ type: Boolean,
123
+ reflect: true,
124
+ },
125
+ /**
126
+ * Displays the fab with rounded corners
127
+ */
128
+ pill: {
129
+ type: Boolean,
130
+ reflect: true,
131
+ },
132
+ /**
133
+ * The distance, in pixels, where the fab should automatically expand
134
+ */
135
+ expandPoint: {
136
+ type: Number,
137
+ attribute: 'expand-point',
138
+ },
139
+ /**
140
+ * The distance, in pixels, where the fab should automatically collapse
141
+ */
142
+ collapsePoint: {
143
+ type: Number,
144
+ attribute: 'collapse-point',
145
+ },
146
+ };
147
+ }
148
+
149
+ constructor() {
150
+ super();
151
+
152
+ this.outlined = false;
153
+ this.disabled = false;
154
+ this.expanded = false;
155
+ }
156
+
157
+ firstUpdated() {
158
+ // events
159
+ window.addEventListener('scroll', this.handleScroll.bind(this));
160
+ this.addEventListener('mouseover', this.handleMouseOver.bind(this));
161
+ this.addEventListener('mouseout', this.handleMouseOut.bind(this));
162
+ }
163
+
164
+ render() {
165
+ return html`
166
+ <button class="button" part="button" ?disabled=${this.disabled} aria-disabled=${this.disabled ? 'true' : 'false'}>
167
+ <span class="icon" part="icon">
168
+ <slot name="icon"></slot>
169
+ </span>
170
+ <span class="text" part="text">
171
+ <slot></slot>
172
+ </span>
173
+ </button>
174
+ `;
175
+ }
176
+
177
+ handleMouseOver() {
178
+ if (!this.disabled) {
179
+ this.expanded = true;
180
+ }
181
+ }
182
+
183
+ handleMouseOut() {
184
+ if (!this.disabled) {
185
+ this.expanded = false;
186
+ }
187
+ }
188
+
189
+ handleScroll() {
190
+ if (window.scrollY > this.expandPoint) {
191
+ this.expanded = true;
192
+ }
193
+
194
+ if (window.scrollY < this.expandPoint) {
195
+ this.expanded = false;
196
+ }
197
+
198
+ if (window.scrollY > this.collapsePoint) {
199
+ this.expanded = false;
200
+ }
201
+
202
+ if (window.scrollY < this.collapsePoint) {
203
+ this.expanded = true;
204
+ }
205
+ }
206
+ }
207
+
208
+ // eslint-disable-next-line no-unused-expressions
209
+ customElements.get('kemet-fab') || customElements.define('kemet-fab', KemetFAB);
@@ -322,6 +322,13 @@ export default class KemetInput extends LitElement {
322
322
  inputType: {
323
323
  type: String,
324
324
  },
325
+ /**
326
+ * Determines if the input is focused
327
+ */
328
+ focused: {
329
+ type: Boolean,
330
+ reflect: true,
331
+ },
325
332
  };
326
333
  }
327
334
 
@@ -336,6 +343,7 @@ export default class KemetInput extends LitElement {
336
343
  this.iconSize = 20;
337
344
  this.validateOnBlur = false;
338
345
  this.isPasswordVisible = false;
346
+ this.focused = false;
339
347
 
340
348
  /**
341
349
  * Used only for form reactive controller
@@ -349,6 +357,10 @@ export default class KemetInput extends LitElement {
349
357
  this.field = this.closest('kemet-field');
350
358
  this.form = this.closest('form');
351
359
  this.slug = this.field ? this.field.slug : 'input';
360
+
361
+ if (this.field) {
362
+ this.field.addEventListener('fmc-password-status', event => this.handleStatus(event));
363
+ }
352
364
  }
353
365
 
354
366
  render() {
@@ -463,7 +475,7 @@ export default class KemetInput extends LitElement {
463
475
  * @private
464
476
  */
465
477
  handleFocus() {
466
- this.hasFocus = true;
478
+ this.focused = true;
467
479
 
468
480
  /**
469
481
  * Fires when the input receives and loses focus
@@ -487,7 +499,7 @@ export default class KemetInput extends LitElement {
487
499
  this.validity = this.input.validity;
488
500
  }
489
501
 
490
- this.hasFocus = false;
502
+ this.focused = false;
491
503
 
492
504
  /**
493
505
  * Fires when the input receives and loses focus
@@ -537,8 +549,7 @@ export default class KemetInput extends LitElement {
537
549
  * Handles when the input receives input
538
550
  * @private
539
551
  */
540
- handleInput(e) {
541
- this.inputType = e.inputType;
552
+ handleInput() {
542
553
  this.value = this.input.value;
543
554
 
544
555
  /**
@@ -0,0 +1,278 @@
1
+ import { html, css, LitElement } from 'lit';
2
+
3
+ export default class KemetPassword extends LitElement {
4
+ static get styles() {
5
+ return [
6
+ css`
7
+ :host {
8
+ color: inherit;
9
+ display: none;
10
+ }
11
+
12
+ :host([show]) {
13
+ display: block;
14
+ }
15
+
16
+ p {
17
+ margin: 1.6rem 0;
18
+ }
19
+
20
+ .status {
21
+ display: grid;
22
+ align-items: center;
23
+ grid-template-columns: 1fr auto;
24
+ line-height: 1;
25
+ margin-top: 0.8rem;
26
+ }
27
+
28
+ .status > span {
29
+ text-transform: capitalize;
30
+ }
31
+
32
+ .status--weak,
33
+ .status--better {
34
+ color: var(--kemet-color-error);
35
+ }
36
+
37
+ .status--strong {
38
+ color: var(--kemet-color-success);
39
+ }
40
+
41
+ .indicator {
42
+ display: flex;
43
+ gap: 0.4rem;
44
+ margin: 0;
45
+ list-style: none;
46
+ }
47
+
48
+ .indicator li {
49
+ width: 1.6rem;
50
+ height: 0.2rem;
51
+ background-color: var(--fds-color--gray2);
52
+ }
53
+
54
+ .status--weak .indicator li:nth-child(1) {
55
+ background-color: var(--kemet-color-error);
56
+ }
57
+
58
+ .status--better .indicator li:nth-child(1),
59
+ .status--better .indicator li:nth-child(2) {
60
+ background-color: var(--kemet-color-error);
61
+ }
62
+
63
+ .status--strong .indicator li {
64
+ background-color: var(--kemet-color-success);
65
+ }
66
+
67
+ .rules {
68
+ color: var(--kemet-color-primary);
69
+ list-style: none;
70
+ font-size: 90%;
71
+ padding-left: 2rem;
72
+ }
73
+
74
+ .rules li {
75
+ position: relative;
76
+ }
77
+
78
+ .rules kemet-icon {
79
+ color: var(--kemet-color-success);
80
+ position: absolute;
81
+ left: -2.4rem;
82
+ }
83
+ `,
84
+ ];
85
+ }
86
+
87
+ static get properties() {
88
+ return {
89
+ /**
90
+ * An array of objects containing the rules the password must meet
91
+ */
92
+ rules: {
93
+ type: Array,
94
+ },
95
+ /**
96
+ * Controls the display of the component
97
+ */
98
+ show: {
99
+ type: Boolean,
100
+ reflect: true,
101
+ },
102
+ /**
103
+ * The value of the input component
104
+ */
105
+ value: {
106
+ type: String,
107
+ },
108
+ /**
109
+ * A message that is above the rules
110
+ */
111
+ message: {
112
+ type: String,
113
+ },
114
+ /**
115
+ * The strength of the password. Weak | Better | Strong
116
+ */
117
+ strength: {
118
+ type: String,
119
+ },
120
+ icon: {
121
+ type: String,
122
+ },
123
+ iconSize: {
124
+ type: Number,
125
+ },
126
+ };
127
+ }
128
+
129
+ constructor() {
130
+ super();
131
+
132
+ // managed properties
133
+ this.rules = [
134
+ { pattern: '(?=.{8,}$)', message: 'At least 8 characters long' },
135
+ { pattern: '(?=.*[a-z])(?=.*[A-Z])', message: 'Uppercase and lowercase' },
136
+ { pattern: '(?=.*[0-9])', message: 'At least one number (0-9)' },
137
+ ];
138
+ this.message = 'Please make sure you meet all the requirements.';
139
+ this.icon = 'check2';
140
+ this.iconSize = 16;
141
+ }
142
+
143
+ firstUpdated() {
144
+ // standard properties
145
+ this.field = this.closest('kemet-field');
146
+ this.input = this.field.querySelector('[slot="input"]');
147
+
148
+ // managed properties
149
+ this.slug = this.field.slug;
150
+
151
+ // events listeners
152
+ this.input.addEventListener('kemet-input-input', this.handleInput.bind(this));
153
+ }
154
+
155
+ render() {
156
+ return html`
157
+ <div role="alert" aria-live="assertive">
158
+ <div class="status status--${this.strength}" part="status">
159
+ <span>${this.strength}</span>
160
+ <ul class="indicator" part="indicator">
161
+ <li></li>
162
+ <li></li>
163
+ <li></li>
164
+ </ul>
165
+ </div>
166
+ <p class="message" part="message">${this.message}</p>
167
+ <ul class="rules" part="rules">
168
+ ${this.makeRules()}
169
+ </ul>
170
+ </div>
171
+ `;
172
+ }
173
+
174
+ /**
175
+ * Makes the list of rules
176
+ * @private
177
+ * @returns {templateResults} the criteria rules
178
+ */
179
+ makeRules() {
180
+ if (this.input) {
181
+ const rules = this.rules.map((rule, index) => {
182
+ const regExp = new RegExp(rule.pattern);
183
+ const meetsCriteria = regExp.test(this.input.value);
184
+ this.rules[index].meetsCriteria = meetsCriteria;
185
+ return html`<li>${this.makeCheckIcon(meetsCriteria)} ${rule.message}</li>`;
186
+ });
187
+
188
+ return rules;
189
+ }
190
+
191
+ return null;
192
+ }
193
+
194
+ /**
195
+ * Makes the check icon if the criteria has been met
196
+ * @param {boolean} meetsCriteria
197
+ * @private
198
+ * @returns {templateResults} an icon component
199
+ */
200
+ makeCheckIcon(meetsCriteria) {
201
+ if (meetsCriteria) {
202
+ return html`<kemet-icon icon=${this.icon} size=${this.iconSize}></kemet-icon>`;
203
+ }
204
+
205
+ return null;
206
+ }
207
+
208
+ /**
209
+ * Handles the kemet-input-input event
210
+ * @param {*} event
211
+ * @private
212
+ */
213
+ handleInput(event) {
214
+ this.value = event.detail;
215
+ this.setStrength();
216
+ this.visibility();
217
+ }
218
+
219
+ /**
220
+ * Determines the strength of the password
221
+ * @private
222
+ */
223
+ setStrength() {
224
+ // We use a setTimeout here because makeRules adds the meetsCriteria
225
+ // property to the rules. We need that to be set by render() before
226
+ // setting the Strength because the percentage calc is based on it
227
+ setTimeout(() => {
228
+ const totalRules = this.rules.length;
229
+ const metRules = this.rules.filter(rule => rule.meetsCriteria).length;
230
+ const metRulesPercentage = Math.round((metRules / totalRules) * 100) / 100;
231
+
232
+ if (metRulesPercentage <= 0.33) {
233
+ this.strength = 'weak';
234
+ this.status = 'error';
235
+ }
236
+
237
+ if (metRulesPercentage > 0.33 && metRulesPercentage <= 0.67) {
238
+ this.strength = 'better';
239
+ this.status = 'error';
240
+ }
241
+
242
+ if (metRulesPercentage > 0.67) {
243
+ this.strength = 'strong';
244
+ this.status = 'success';
245
+ }
246
+
247
+ /**
248
+ * Fires when there's a change in status.
249
+ */
250
+ this.dispatchEvent(
251
+ new CustomEvent('kemet-password-status', {
252
+ bubbles: true,
253
+ composed: true,
254
+ detail: {
255
+ status: this.status,
256
+ validity: { meetsPasswordCriteria: this.status === 'success' },
257
+ element: this,
258
+ },
259
+ }),
260
+ );
261
+ }, 1);
262
+ }
263
+
264
+ /**
265
+ * Determines whether to show or hide the component
266
+ * @private
267
+ */
268
+ visibility() {
269
+ if (this.value.length > 0) {
270
+ this.show = true;
271
+ } else {
272
+ this.show = false;
273
+ }
274
+ }
275
+ }
276
+
277
+ // eslint-disable-next-line no-unused-expressions
278
+ customElements.get('kemet-password') || customElements.define('kemet-password', KemetPassword);