@citizenplane/pimp 8.0.0 → 8.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@citizenplane/pimp",
3
- "version": "8.0.0",
3
+ "version": "8.0.1",
4
4
  "scripts": {
5
5
  "dev": "vite --host",
6
6
  "build": "vite build",
@@ -25,32 +25,32 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "vue": "^3.2.26",
28
+ "vue": "^3.2.29",
29
29
  "animejs": "^3.2.0",
30
- "core-js": "^3.20.1",
30
+ "core-js": "^3.20.3",
31
31
  "feather-icons": "^4.28.0",
32
- "luxon": "^2.2.0",
32
+ "luxon": "^2.3.0",
33
33
  "maska": "^1.5.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@babel/core": "^7.16.5",
37
- "@vitejs/plugin-vue": "^2.0.1",
36
+ "@babel/core": "^7.16.12",
37
+ "@vitejs/plugin-vue": "^2.1.0",
38
38
  "@vue/babel-preset-app": "^4.5.15",
39
39
  "@vue/eslint-config-prettier": "^7.0.0",
40
40
  "@vue/test-utils": "^2.0.0-rc.18",
41
41
  "babel-core": "^7.0.0-bridge.0",
42
- "eslint": "^8.5.0",
42
+ "eslint": "^8.7.0",
43
43
  "eslint-plugin-prettier": "^4.0.0",
44
- "eslint-plugin-vue": "^8.2.0",
44
+ "eslint-plugin-vue": "^8.4.0",
45
45
  "husky": "^7.0.4",
46
46
  "jest": "^26.6.3",
47
- "lint-staged": "^12.1.4",
47
+ "lint-staged": "^12.3.2",
48
48
  "prettier": "^2.5.1",
49
- "sass": "~1.45.1",
49
+ "sass": "~1.49.0",
50
50
  "sass-loader": "^12.4.0",
51
51
  "stylus": "^0.56.0",
52
52
  "stylus-loader": "^3.0.2",
53
- "vite": "^2.7.7",
53
+ "vite": "^2.7.13",
54
54
  "vue-jest": "^5.0.0-alpha.10"
55
55
  }
56
56
  }
@@ -214,30 +214,6 @@ button:-moz-focusring,
214
214
  outline: 1px dotted ButtonText;
215
215
  }
216
216
 
217
- /**
218
- * Correct the padding in Firefox.
219
- */
220
-
221
- fieldset {
222
- padding: 0.35em 0.75em 0.625em;
223
- }
224
-
225
- /**
226
- * 1. Correct the text wrapping in Edge and IE.
227
- * 2. Correct the color inheritance from `fieldset` elements in IE.
228
- * 3. Remove the padding so developers are not caught out when they zero out
229
- * `fieldset` elements in all browsers.
230
- */
231
-
232
- legend {
233
- box-sizing: border-box; /* 1 */
234
- color: inherit; /* 2 */
235
- display: table; /* 1 */
236
- max-width: 100%; /* 1 */
237
- padding: 0; /* 3 */
238
- white-space: normal; /* 1 */
239
- }
240
-
241
217
  /**
242
218
  * Add the correct vertical alignment in Chrome, Firefox, and Opera.
243
219
  */
@@ -6,7 +6,7 @@ $gray-3: #c6c6ce;
6
6
  $gray-4: #d8d8de;
7
7
  $gray-5: #ececef;
8
8
 
9
- $red: #fc5959;
9
+ $red: #eb0505;
10
10
  $orange: #ffa800;
11
11
  $yellow: #ffd00b;
12
12
  $green-2: #00c582;
@@ -200,7 +200,7 @@ export default {
200
200
  padding: pxToEm(12) pxToEm(16);
201
201
  min-width: 10ch;
202
202
  height: $component-size-default;
203
- font-size: pxToRem(16);
203
+ font-size: inherit;
204
204
  line-height: 1.1;
205
205
  text-decoration: none;
206
206
  color: $neutral-dark;
@@ -19,7 +19,6 @@ export default {
19
19
 
20
20
  <style lang="scss">
21
21
  .baseInputLabel {
22
- padding: pxToEm(6) 0;
23
22
  display: block;
24
23
  font-size: pxToEm(14);
25
24
  color: $neutral-dark;
@@ -5,7 +5,7 @@
5
5
  Default date input: {{ cpDate }}
6
6
  </cp-heading>
7
7
  <div class="sectionDatePickers__datepickers">
8
- <cp-date v-model="cpDate" label="Default date input" class="sectionDatePickers__date" />
8
+ <cp-date v-model="cpDate" label="Default date input" class="sectionDatePickers__date" :is-invalid="false" />
9
9
  </div>
10
10
  </div>
11
11
  <div class="sectionDatePickers__type">
@@ -18,7 +18,7 @@
18
18
  </div>
19
19
  <div class="sectionDatePickers__datepickers">
20
20
  <div class="sectionDatePickers__wrapper">
21
- <cp-input input-id="datepicker-trigger" label="CoreDatepicker" placeholder="CoreDatepicker" />
21
+ <cp-input id="datepicker-trigger" label="CoreDatepicker" placeholder="CoreDatepicker" />
22
22
  <cp-core-datepicker
23
23
  class="sectionDatePickers__custom"
24
24
  mode="range"
@@ -70,7 +70,12 @@
70
70
  <div class="sectionSimpleInputs__type">
71
71
  <cp-heading heading-level="h3" :size="600" class="sectionSimpleInputs__title">Disabled</cp-heading>
72
72
  <div class="sectionSimpleInputs__inputs">
73
- <cp-input label="Empty disabled input with icon before" type="text" placeholder="Input placeholder" disabled>
73
+ <cp-input
74
+ label="Empty disabled input with icon before"
75
+ type="text"
76
+ placeholder="Input placeholder"
77
+ :disabled="true"
78
+ >
74
79
  <template #input-icon><cp-icon type="map-pin" /></template>
75
80
  </cp-input>
76
81
  <cp-input
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <div class="cpCalendar">
3
3
  <cp-input
4
+ :id="triggerElementId"
4
5
  type="text"
5
- :input-id="triggerElementId"
6
6
  :model-value="humanDateFormat(dateOne, dateTwo)"
7
7
  placeholder="Select a date"
8
8
  :is-invalid="isError"
@@ -5,22 +5,26 @@
5
5
  </label>
6
6
  <div class="cpDate__inputs">
7
7
  <input
8
- ref="day"
9
8
  v-model="day"
9
+ v-maska="'##'"
10
10
  placeholder="DD"
11
11
  class="cpDate__day"
12
- type="number"
13
12
  inputmode="numeric"
14
- :min="1"
15
- :max="31"
16
13
  maxlength="2"
17
14
  :required="required"
18
15
  :disabled="disabled"
16
+ :autocomplete="autocompleteFields.day"
19
17
  />
20
18
  <div class="cpDate__divider" />
21
19
  <div class="cpDate__month" :class="selectDynamicClass">
22
- <select :id="cpDateId" v-model="month" :required="required" :disabled="disabled">
23
- <option value disabled>Month</option>
20
+ <select
21
+ :id="cpDateId"
22
+ v-model="month"
23
+ :required="required"
24
+ :disabled="disabled"
25
+ :autocomplete="autocompleteFields.month"
26
+ >
27
+ <option value>Month</option>
24
28
  <option v-for="(monthItem, index) in months" :key="index" :value="monthItem.value">
25
29
  {{ monthItem.label }}
26
30
  </option>
@@ -28,21 +32,19 @@
28
32
  </div>
29
33
  <div class="cpDate__divider" />
30
34
  <input
31
- ref="year"
32
35
  v-model="year"
36
+ v-maska="'####'"
33
37
  placeholder="YYYY"
34
38
  class="cpDate__year"
35
- type="number"
36
39
  inputmode="numeric"
37
- :min="minYear"
38
- :max="maxYear"
39
40
  maxlength="4"
40
41
  :disabled="disabled"
41
42
  :required="required"
43
+ :autocomplete="autocompleteFields.year"
42
44
  />
43
45
  </div>
44
46
  <transition-expand>
45
- <div v-if="!isDateValid" class="cpDate__errorMessage">{{ errorMessage }}</div>
47
+ <div v-if="advancedErrorMessage" class="cpDate__errorMessage">{{ advancedErrorMessage }}</div>
46
48
  </transition-expand>
47
49
  </div>
48
50
  </template>
@@ -80,6 +82,18 @@ export default {
80
82
  type: Boolean,
81
83
  default: false,
82
84
  },
85
+ isInvalid: {
86
+ type: Boolean,
87
+ default: false,
88
+ },
89
+ errorMessage: {
90
+ type: String,
91
+ default: '',
92
+ },
93
+ autocompleteBirthday: {
94
+ type: Boolean,
95
+ default: false,
96
+ },
83
97
  },
84
98
  emits: ['update:modelValue', 'on-validation'],
85
99
  data() {
@@ -144,13 +158,16 @@ export default {
144
158
  return this.day === '' && this.month === '' && this.year === ''
145
159
  },
146
160
  isDateValid() {
147
- if (!this.required && this.areInputsEmpty) return true
161
+ if (this.areInputsEmpty && !this.errorMessage) return true
148
162
 
149
- const isValid =
150
- this.isDayValid && this.isMonthValid && this.isYearValid && this.isDateBeforeMaxDate && this.isDateAfterMinDate
151
- this.$emit('on-validation', isValid)
152
-
153
- return isValid
163
+ return (
164
+ !this.isInvalid &&
165
+ this.isDayValid &&
166
+ this.isMonthValid &&
167
+ this.isYearValid &&
168
+ this.isDateBeforeMaxDate &&
169
+ this.isDateAfterMinDate
170
+ )
154
171
  },
155
172
  isDayValid() {
156
173
  return this.day >= 1 && this.day <= this.monthMaxDay
@@ -164,10 +181,10 @@ export default {
164
181
  areAllFieldsEmpty() {
165
182
  return !this.day && !this.month && !this.year
166
183
  },
167
- errorMessage() {
168
- if (this.areAllFieldsEmpty && this.required) {
169
- return `The ${this.label} field is required.`
170
- }
184
+ advancedErrorMessage() {
185
+ if (this.isDateValid) return ''
186
+
187
+ if (this.errorMessage) return this.errorMessage
171
188
 
172
189
  if (!this.isMonthValid) {
173
190
  return 'Month is required.'
@@ -204,6 +221,15 @@ export default {
204
221
  'cpDate__month--isEmpty': !this.month,
205
222
  }
206
223
  },
224
+ autocompleteFields() {
225
+ if (!this.autocompleteBirthday) return 'off'
226
+
227
+ return {
228
+ day: 'bday-day',
229
+ month: 'bday-month',
230
+ year: 'bday-year',
231
+ }
232
+ },
207
233
  },
208
234
  watch: {
209
235
  day() {
@@ -211,7 +237,6 @@ export default {
211
237
  },
212
238
  month() {
213
239
  this.handleUpdate()
214
- this.focusOnFirstEmptyInput()
215
240
  },
216
241
  year() {
217
242
  this.handleUpdate()
@@ -224,17 +249,9 @@ export default {
224
249
  return DateTime.fromISO(this.modelValue)[token]
225
250
  },
226
251
  handleUpdate() {
227
- const dateValue = this.isDateValid ? this.isoDate : 'Invalid Datetime'
252
+ this.$emit('update:modelValue', this.isoDate)
228
253
 
229
- this.$emit('update:modelValue', dateValue)
230
- },
231
- focusOnFirstEmptyInput() {
232
- if (!this.isDayValid) {
233
- this.$refs.day.focus()
234
- return
235
- }
236
-
237
- if (!this.isYearValid) this.$refs.year.focus()
254
+ this.$emit('on-validation', this.isDateValid)
238
255
  },
239
256
  },
240
257
  }
@@ -273,14 +290,14 @@ export default {
273
290
  .cpDate {
274
291
  input::placeholder,
275
292
  &__month--isEmpty {
276
- color: $neutral-dark-1;
293
+ color: $neutral-dark;
277
294
  }
278
295
 
279
296
  &__label {
280
297
  display: flex;
281
298
  align-items: center;
282
- margin: pxToRem(6) 0;
283
- font-size: pxToRem(14);
299
+ margin-bottom: pxToEm(6);
300
+ font-size: pxToEm(14);
284
301
 
285
302
  svg {
286
303
  margin-left: $space-sm;
@@ -331,8 +348,8 @@ export default {
331
348
  position: absolute;
332
349
  top: 50%;
333
350
  right: pxToEm(12);
334
- width: pxToRem(20);
335
- height: pxToRem(20);
351
+ width: pxToEm(20);
352
+ height: pxToEm(20);
336
353
  background-image: url('@/assets/images/icons/chevron-down-icon.svg');
337
354
  background-size: cover;
338
355
  transform: translateY(-50%);
@@ -343,9 +360,14 @@ export default {
343
360
  select {
344
361
  @extend %u-text-ellipsis;
345
362
  width: 100%;
346
- padding: pxToRem(12) pxToRem(40) pxToRem(12) pxToRem(12);
363
+ padding: pxToEm(12) pxToEm(40) pxToEm(12) pxToEm(12);
347
364
  color: inherit;
348
365
  cursor: pointer;
366
+
367
+ &:focus-visible {
368
+ text-decoration: underline;
369
+ font-weight: 500;
370
+ }
349
371
  }
350
372
 
351
373
  select > option:not(:disabled) {
@@ -395,7 +417,7 @@ export default {
395
417
  margin-top: pxToRem(6);
396
418
  color: $error-color;
397
419
  font-weight: 500;
398
- font-size: pxToRem(14);
420
+ font-size: pxToEm(14);
399
421
  }
400
422
  }
401
423
  </style>
@@ -2,9 +2,9 @@
2
2
  <div class="cpDatepicker">
3
3
  <cp-input
4
4
  v-show="!isInline"
5
+ :id="datePickerReferenceId"
5
6
  :model-value="inputComputedValue"
6
7
  type="text"
7
- :input-id="datePickerReferenceId"
8
8
  :placeholder="placeholder"
9
9
  :is-invalid="isError"
10
10
  :error-message="errorMessage"
@@ -19,6 +19,7 @@ export default {
19
19
  intent: {
20
20
  type: String,
21
21
  required: true,
22
+ default: Intent.INFO.value,
22
23
  validator(value) {
23
24
  const intentValues = Object.values(Intent).map((item) => item.value)
24
25
  return intentValues.includes(value)
@@ -78,7 +79,7 @@ export default {
78
79
  display: flex;
79
80
  align-items: flex-start;
80
81
  padding: pxToRem(10);
81
- font-size: pxToRem(14);
82
+ font-size: pxToEm(14);
82
83
  border-radius: pxToRem(4);
83
84
  overflow: hidden;
84
85
 
@@ -1,6 +1,8 @@
1
1
  <template>
2
- <div class="cpInput" :class="dynamicClasses" :aria-disabled="disabled" @click="focusOnInput">
3
- <base-input-label v-if="label" :for="inputReferenceId"> {{ inputLabelTitle }}</base-input-label>
2
+ <div class="cpInput" :class="dynamicClasses" :aria-disabled="isDisabled" @click="focusOnInput">
3
+ <base-input-label v-if="label" :for="inputIdentifier" class="cpInput__label">
4
+ {{ inputLabelTitle }}
5
+ </base-input-label>
4
6
  <div
5
7
  ref="cpInputContainer"
6
8
  :class="{ 'cpInput__container--hasBeforeIcon': hasBeforeIcon }"
@@ -19,23 +21,21 @@
19
21
  <slot name="input-icon-after" />
20
22
  </div>
21
23
  <input
22
- :id="inputReferenceId"
24
+ :id="inputIdentifier"
23
25
  v-maska="mask"
24
- :disabled="disabled"
25
- :name="name"
26
- :placeholder="placeholder"
27
- :readonly="readonly"
28
- :required="required"
29
- :autocomplete="autocomplete"
30
- :inputmode="inputMode"
31
- :type="type"
32
26
  :value="modelValue"
27
+ v-bind="restAttributes"
33
28
  class="cpInput__inner"
34
29
  @input="handleChange"
35
30
  />
36
31
  </div>
37
32
  <transition-expand>
38
- <base-input-label v-if="isInputInvalid && errorMessage" :is-invalid="isInvalid" :for="inputReferenceId">
33
+ <base-input-label
34
+ v-if="isInputInvalid && errorMessage"
35
+ :is-invalid="isInvalid"
36
+ :for="inputIdentifier"
37
+ class="cpInput__label cpInput__label--isAfter"
38
+ >
39
39
  {{ errorMessage }}
40
40
  </base-input-label>
41
41
  </transition-expand>
@@ -43,27 +43,14 @@
43
43
  </template>
44
44
 
45
45
  <script>
46
+ import { ref } from 'vue'
47
+
46
48
  import { randomString } from '@/helpers'
47
49
 
48
50
  import BaseInputLabel from '@/components/core/BaseInputLabel/index.vue'
49
51
  import CpIcon from '@/components/visual/CpIcon.vue'
50
52
  import TransitionExpand from '@/components/helpers-utilities/TransitionExpand.vue'
51
53
 
52
- const inputTypesList = [
53
- 'text',
54
- 'date',
55
- 'datetime-local',
56
- 'email',
57
- 'month',
58
- 'number',
59
- 'password',
60
- 'search',
61
- 'tel',
62
- 'time',
63
- 'url',
64
- 'week',
65
- ]
66
-
67
54
  export default {
68
55
  name: 'CpInput',
69
56
  components: {
@@ -71,6 +58,7 @@ export default {
71
58
  BaseInputLabel,
72
59
  TransitionExpand,
73
60
  },
61
+ inheritAttrs: false,
74
62
  props: {
75
63
  modelValue: {
76
64
  type: [String, Number, Boolean],
@@ -80,46 +68,10 @@ export default {
80
68
  type: String,
81
69
  default: '',
82
70
  },
83
- placeholder: {
84
- type: String,
85
- default: '',
86
- required: true,
87
- },
88
- required: {
89
- type: Boolean,
90
- default: false,
91
- },
92
71
  inputId: {
93
72
  type: String,
94
- default: '',
95
- },
96
- type: {
97
- type: String,
98
- default: inputTypesList[0],
99
- validator: (value) => {
100
- return inputTypesList.includes(value)
101
- },
102
- },
103
- name: {
104
- type: String,
105
- default: '',
106
- },
107
- readonly: {
108
- type: Boolean,
109
- default: false,
110
- },
111
- disabled: {
112
- type: Boolean,
113
73
  default: null,
114
74
  },
115
- autocomplete: {
116
- type: String,
117
- default: 'on',
118
- },
119
- inputMode: {
120
- type: String,
121
- default: 'text',
122
- },
123
75
  isInvalid: {
124
76
  type: Boolean,
125
77
  default: false,
@@ -146,18 +98,38 @@ export default {
146
98
  },
147
99
  },
148
100
  emits: ['update:modelValue'],
101
+ setup(props, { attrs }) {
102
+ // class is a reserved work, we can't remove 'class' property from attrs
103
+ // eslint-disable-next-line no-unused-vars
104
+ const { ['class']: value, id, ...restAttributes } = attrs
105
+ const inputIdentifier = id === undefined ? ref(randomString()) : id
106
+
107
+ return {
108
+ inputIdentifier,
109
+ restAttributes,
110
+ }
111
+ },
149
112
  data() {
150
113
  return {
151
- inputReferenceId: this.inputId,
152
114
  isDOMElementValid: true,
153
115
  }
154
116
  },
155
117
  computed: {
118
+ isDisabled() {
119
+ return this.checkAttribute('disabled')
120
+ },
121
+ isRequired() {
122
+ return this.checkAttribute('required')
123
+ },
124
+ isReadonly() {
125
+ return this.checkAttribute('readonly')
126
+ },
156
127
  dynamicClasses() {
157
128
  return [
129
+ this.$attrs.class,
158
130
  {
159
131
  'cpInput--isInvalid': this.isInputInvalid,
160
- 'cpInput--isDisabled': this.disabled,
132
+ 'cpInput--isDisabled': this.isDisabled,
161
133
  'cpInput--hasNoBorder': this.removeBorder,
162
134
  'cpInput--isLarge': this.isLarge,
163
135
  },
@@ -171,12 +143,14 @@ export default {
171
143
  }
172
144
  },
173
145
  inputLabelTitle() {
174
- const requiredLabel = this.required && this.label ? '*' : ''
146
+ if (this.label === '') return ''
147
+
148
+ const requiredMark = this.isRequired ? '*' : ''
175
149
 
176
- return `${this.label} ${requiredLabel}`
150
+ return `${this.label} ${requiredMark}`
177
151
  },
178
152
  isValid() {
179
- return this.modelValue && !this.readonly && !this.isInvalid && this.isDOMElementValid
153
+ return this.modelValue && !this.isReadonly && !this.isInvalid && this.isDOMElementValid
180
154
  },
181
155
  isInputInvalid() {
182
156
  return this.isInvalid || !this.isDOMElementValid
@@ -188,15 +162,11 @@ export default {
188
162
  return !!this.$slots['input-icon-after']
189
163
  },
190
164
  DOMElement() {
191
- return this.$refs.cpInputContainer.children.namedItem(this.inputReferenceId)
165
+ return this.$refs.cpInputContainer.children.namedItem(this.inputIdentifier)
192
166
  },
193
167
  },
194
168
  mounted() {
195
- if (!this.inputReferenceId) this.inputReferenceId = randomString()
196
-
197
- this.$nextTick(() => {
198
- this.checkInputValidity()
199
- })
169
+ this.$nextTick(() => this.checkInputValidity())
200
170
  },
201
171
  methods: {
202
172
  handleChange(e) {
@@ -214,6 +184,9 @@ export default {
214
184
  (this.DOMElement.validity && this.DOMElement.validity.valid) ||
215
185
  (this.DOMElement.validity && this.DOMElement.validity.valueMissing)
216
186
  },
187
+ checkAttribute(attribute) {
188
+ return this.$attrs[attribute] === '' || this.$attrs[attribute] === true
189
+ },
217
190
  },
218
191
  }
219
192
  </script>
@@ -229,7 +202,6 @@ export default {
229
202
  position: relative;
230
203
  display: flex;
231
204
  flex-direction: column;
232
- font-size: pxToRem(16);
233
205
 
234
206
  &__container {
235
207
  z-index: 1;
@@ -310,6 +282,18 @@ export default {
310
282
  box-shadow: none;
311
283
  }
312
284
 
285
+ &__label {
286
+ display: block;
287
+
288
+ &:not(#{&}--isAfter) {
289
+ margin-bottom: pxToEm(6);
290
+ }
291
+
292
+ &--isAfter {
293
+ margin-top: pxToEm(6);
294
+ }
295
+ }
296
+
313
297
  $cp-input-icon-size: pxToEm(20);
314
298
 
315
299
  &__icon {
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="cpTextarea">
3
- <base-input-label v-if="label" :for="inputReferenceId">
3
+ <base-input-label v-if="label" :for="inputReferenceId" class="cpTextarea__label">
4
4
  {{ inputLabelTitle }}
5
5
  </base-input-label>
6
6
  <textarea
@@ -15,7 +15,12 @@
15
15
  @input="handleChange"
16
16
  />
17
17
  <transition-expand>
18
- <base-input-label v-if="isInvalid" :for="inputReferenceId" is-invalid>
18
+ <base-input-label
19
+ v-if="isInvalid"
20
+ :for="inputReferenceId"
21
+ is-invalid
22
+ class="cpTextarea__label cpTextarea__label--isAfter"
23
+ >
19
24
  {{ errorMessage }}
20
25
  </base-input-label>
21
26
  </transition-expand>
@@ -153,5 +158,17 @@ export default {
153
158
  }
154
159
  }
155
160
  }
161
+
162
+ &__label {
163
+ display: block;
164
+
165
+ &:not(#{&}--isAfter) {
166
+ margin-bottom: pxToEm(6);
167
+ }
168
+
169
+ &--isAfter {
170
+ margin-top: pxToEm(6);
171
+ }
172
+ }
156
173
  }
157
174
  </style>