@zipify/wysiwyg 1.0.0-dev.17 → 1.0.0-dev.18

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 (98) hide show
  1. package/.github/dependabot.yaml +1 -0
  2. package/dist/wysiwyg.css +199 -12
  3. package/dist/wysiwyg.js +1 -1
  4. package/example/ExampleApp.vue +6 -2
  5. package/example/pageBlocks.js +31 -0
  6. package/example/presets.js +2 -2
  7. package/lib/Wysiwyg.vue +14 -3
  8. package/lib/assets/icons/link.svg +3 -0
  9. package/lib/assets/icons/unlink.svg +3 -0
  10. package/lib/components/base/Button.vue +21 -1
  11. package/lib/components/base/Checkbox.vue +89 -0
  12. package/lib/components/base/FieldLabel.vue +2 -1
  13. package/lib/components/base/Icon.vue +2 -2
  14. package/lib/components/base/Modal.vue +0 -1
  15. package/lib/components/base/TextField.vue +106 -0
  16. package/lib/components/base/__tests__/TextField.test.js +57 -0
  17. package/lib/components/base/__tests__/__snapshots__/TextField.test.js.snap +9 -0
  18. package/lib/components/base/composables/index.js +1 -0
  19. package/lib/components/base/composables/useValidator.js +19 -0
  20. package/lib/components/base/dropdown/Dropdown.vue +15 -3
  21. package/lib/components/base/dropdown/DropdownActivator.vue +19 -3
  22. package/lib/components/base/index.js +2 -0
  23. package/lib/components/toolbar/Toolbar.vue +5 -5
  24. package/lib/components/toolbar/ToolbarFull.vue +10 -2
  25. package/lib/components/toolbar/controls/FontSizeControl.vue +7 -0
  26. package/lib/components/toolbar/controls/UnderlineControl.vue +2 -2
  27. package/lib/components/toolbar/controls/__tests__/UnderlineControl.test.js +4 -0
  28. package/lib/components/toolbar/controls/index.js +1 -0
  29. package/lib/components/toolbar/controls/link/LinkControl.vue +152 -0
  30. package/lib/components/toolbar/controls/link/LinkControlApply.vue +35 -0
  31. package/lib/components/toolbar/controls/link/LinkControlHeader.vue +67 -0
  32. package/lib/components/toolbar/controls/link/composables/index.js +1 -0
  33. package/lib/components/toolbar/controls/link/composables/useLink.js +61 -0
  34. package/lib/components/toolbar/controls/link/destination/LinkControlDestination.vue +103 -0
  35. package/lib/components/toolbar/controls/link/destination/LinkControlPageBlock.vue +54 -0
  36. package/lib/components/toolbar/controls/link/destination/LinkControlUrl.vue +52 -0
  37. package/lib/components/toolbar/controls/link/destination/index.js +1 -0
  38. package/lib/components/toolbar/controls/link/index.js +1 -0
  39. package/lib/composables/__tests__/useEditor.test.js +2 -2
  40. package/lib/composables/useEditor.js +2 -4
  41. package/lib/enums/LinkDestinations.js +4 -0
  42. package/lib/enums/LinkTargets.js +4 -0
  43. package/lib/enums/TextSettings.js +3 -1
  44. package/lib/enums/index.js +2 -0
  45. package/lib/extensions/Alignment.js +18 -6
  46. package/lib/extensions/BackgroundColor.js +14 -6
  47. package/lib/extensions/FontColor.js +14 -6
  48. package/lib/extensions/FontFamily.js +25 -8
  49. package/lib/extensions/FontSize.js +26 -13
  50. package/lib/extensions/FontStyle.js +23 -13
  51. package/lib/extensions/FontWeight.js +22 -14
  52. package/lib/extensions/LineHeight.js +11 -3
  53. package/lib/extensions/Link.js +101 -0
  54. package/lib/extensions/StylePreset.js +4 -2
  55. package/lib/extensions/TextDecoration.js +27 -12
  56. package/lib/extensions/__tests__/Alignment.test.js +11 -5
  57. package/lib/extensions/__tests__/BackgroundColor.test.js +11 -5
  58. package/lib/extensions/__tests__/CaseStyle.test.js +3 -5
  59. package/lib/extensions/__tests__/FontColor.test.js +11 -5
  60. package/lib/extensions/__tests__/FontFamily.test.js +32 -7
  61. package/lib/extensions/__tests__/FontSize.test.js +11 -5
  62. package/lib/extensions/__tests__/FontStyle.test.js +11 -5
  63. package/lib/extensions/__tests__/FontWeight.test.js +11 -5
  64. package/lib/extensions/__tests__/LineHeight.test.js +11 -5
  65. package/lib/extensions/__tests__/StylePreset.test.js +70 -6
  66. package/lib/extensions/__tests__/TextDecoration.test.js +27 -5
  67. package/lib/extensions/__tests__/__snapshots__/Alignment.test.js.snap +26 -2
  68. package/lib/extensions/__tests__/__snapshots__/BackgroundColor.test.js.snap +30 -1
  69. package/lib/extensions/__tests__/__snapshots__/FontColor.test.js.snap +18 -1
  70. package/lib/extensions/__tests__/__snapshots__/FontFamily.test.js.snap +88 -1
  71. package/lib/extensions/__tests__/__snapshots__/FontSize.test.js.snap +33 -2
  72. package/lib/extensions/__tests__/__snapshots__/FontStyle.test.js.snap +25 -4
  73. package/lib/extensions/__tests__/__snapshots__/FontWeight.test.js.snap +30 -1
  74. package/lib/extensions/__tests__/__snapshots__/LineHeight.test.js.snap +26 -2
  75. package/lib/extensions/__tests__/__snapshots__/StylePreset.test.js.snap +6 -2
  76. package/lib/extensions/__tests__/__snapshots__/TextDecoration.test.js.snap +93 -3
  77. package/lib/extensions/core/CopyPasteProcessor.js +10 -0
  78. package/lib/extensions/core/TextProcessor.js +10 -0
  79. package/lib/extensions/core/__tests__/NodeProcessor.test.js +3 -5
  80. package/lib/extensions/core/__tests__/SelectionProcessor.test.js +3 -5
  81. package/lib/extensions/core/__tests__/TextProcessor.test.js +138 -12
  82. package/lib/extensions/core/__tests__/__snapshots__/TextProcessor.test.js.snap +26 -0
  83. package/lib/extensions/core/index.js +11 -2
  84. package/lib/extensions/core/plugins/PastePlugin.js +48 -0
  85. package/lib/extensions/core/plugins/ProseMirrorPlugin.js +20 -0
  86. package/lib/extensions/core/plugins/index.js +1 -0
  87. package/lib/extensions/index.js +41 -34
  88. package/lib/extensions/list/__tests__/List.test.js +3 -5
  89. package/lib/extensions/list/__tests__/__snapshots__/List.test.js.snap +45 -15
  90. package/lib/injectionTokens.js +2 -1
  91. package/lib/services/ContentNormalizer.js +62 -20
  92. package/lib/services/__tests__/ContentNormalizer.test.js +40 -7
  93. package/lib/styles/content.css +17 -9
  94. package/lib/styles/helpers/offsets.css +16 -0
  95. package/lib/styles/variables.css +6 -0
  96. package/lib/utils/__tests__/__snapshots__/renderInlineSetting.test.js.snap +4 -4
  97. package/lib/utils/renderInlineSetting.js +1 -1
  98. package/package.json +11 -9
@@ -29,6 +29,7 @@
29
29
  :make-preset-variable="$options.makePresetVariable"
30
30
  :favorite-colors="favoriteColors"
31
31
  :device="device"
32
+ :page-blocks="pageBlocks"
32
33
  :active="isActive"
33
34
  @updateFavoriteColors="updateFavoriteColors"
34
35
  />
@@ -41,6 +42,7 @@ import { computed, onMounted, ref } from '@vue/composition-api';
41
42
  import { Wysiwyg } from '../lib';
42
43
  import { FONTS } from './fonts';
43
44
  import { PRESETS, renderPresetVariable } from './presets';
45
+ import { PAGE_BLOCKS } from './pageBlocks';
44
46
 
45
47
  function getInitialContent() {
46
48
  const data = sessionStorage.getItem('wswg-data');
@@ -65,6 +67,7 @@ export default {
65
67
  const wswgRef = ref(null);
66
68
  const content = ref(getInitialContent());
67
69
  const presets = ref(PRESETS);
70
+ const pageBlocks = ref(PAGE_BLOCKS);
68
71
  const device = ref('desktop');
69
72
  const updatedAt = new Date(ZW_UPDATED_AT).toLocaleString('ua-UA');
70
73
  const isActive = ref(false);
@@ -96,7 +99,7 @@ export default {
96
99
  }
97
100
  }
98
101
 
99
- document.body.addEventListener('click', (event) => {
102
+ document.addEventListener('click', (event) => {
100
103
  isActive.value = wswgRef.value.$el.contains(event.target);
101
104
  });
102
105
 
@@ -111,7 +114,8 @@ export default {
111
114
  device,
112
115
  updatedAt,
113
116
  presets,
114
- isActive
117
+ isActive,
118
+ pageBlocks
115
119
  };
116
120
  }
117
121
  };
@@ -0,0 +1,31 @@
1
+ export const PAGE_BLOCKS = [
2
+ {
3
+ id: 123,
4
+ title: 'Dynamic Buy Box'
5
+ },
6
+
7
+ {
8
+ id: 123456,
9
+ title: 'Text + Navigation'
10
+ },
11
+
12
+ {
13
+ id: 11123,
14
+ title: 'Dynamic Bundle Offer'
15
+ },
16
+
17
+ {
18
+ id: 4444444,
19
+ title: 'Dynamic Buy Box[2]'
20
+ },
21
+
22
+ {
23
+ id: 555555,
24
+ title: 'Image'
25
+ },
26
+
27
+ {
28
+ id: 666666,
29
+ title: 'Footer'
30
+ }
31
+ ];
@@ -14,7 +14,7 @@ export const PRESETS = [
14
14
  'common': {
15
15
  'font_family': 'Lato',
16
16
  'font_weight': '700',
17
- 'color': '#262626',
17
+ 'color': '',
18
18
  'font_style': 'normal',
19
19
  'text_decoration': 'none'
20
20
  },
@@ -131,7 +131,7 @@ export const PRESETS = [
131
131
  'common': {
132
132
  'font_family': 'inherit',
133
133
  'font_weight': 'inherit',
134
- 'color': '',
134
+ 'color': 'rgb(0, 0, 238)',
135
135
  'font_style': 'normal',
136
136
  'text_decoration': 'underline'
137
137
  },
package/lib/Wysiwyg.vue CHANGED
@@ -15,7 +15,7 @@ import { EditorContent } from '@tiptap/vue-2';
15
15
  import { provide, toRef, ref } from '@vue/composition-api';
16
16
  import { Toolbar } from './components';
17
17
  import { useToolbar, useEditor } from './composables';
18
- import { getExtensions } from './extensions';
18
+ import { buildExtensions } from './extensions';
19
19
  import { InjectionTokens } from './injectionTokens';
20
20
  import { ContextWindow, FavoriteColors, Storage } from './services';
21
21
  import { Devices } from './enums';
@@ -97,6 +97,11 @@ export default {
97
97
  default: 'zw-list--'
98
98
  },
99
99
 
100
+ pageBlocks: {
101
+ type: Array,
102
+ required: true
103
+ },
104
+
100
105
  // Requires Window type but it different in iframe and outside
101
106
  // eslint-disable-next-line vue/require-prop-types
102
107
  window: {
@@ -109,6 +114,7 @@ export default {
109
114
  ContextWindow.use(props.window);
110
115
 
111
116
  const fonts = props.fonts.map((font) => new Font(font));
117
+ const presets = toRef(props, 'presets');
112
118
 
113
119
  const toolbarRef = ref(null);
114
120
  const wysiwygRef = ref(null);
@@ -124,11 +130,13 @@ export default {
124
130
  toolbar.update();
125
131
  }
126
132
 
133
+ const pageBlocks = toRef(props, 'pageBlocks');
134
+
127
135
  const editor = useEditor({
128
136
  content: toRef(props, 'value'),
129
137
  onChange: (content) => onChange(content),
130
138
 
131
- extensions: getExtensions({
139
+ extensions: buildExtensions({
132
140
  fonts,
133
141
  minFontSize: MIN_FONT_SIZE,
134
142
  maxFontSize: MAX_FONT_SIZE,
@@ -137,7 +145,9 @@ export default {
137
145
  makePresetVariable: props.makePresetVariable,
138
146
  basePresetClass: props.basePresetClass,
139
147
  baseListClass: props.baseListClass,
140
- deviceRef: toRef(props, 'device')
148
+ linkPreset: presets.value.find((preset) => preset.id === 'link'),
149
+ deviceRef: toRef(props, 'device'),
150
+ pageBlocks
141
151
  })
142
152
  });
143
153
 
@@ -155,6 +165,7 @@ export default {
155
165
  provide(InjectionTokens.FONT_SIZES, fontSizes);
156
166
  provide(InjectionTokens.LOCAL_STORAGE, new Storage(localStorage));
157
167
  provide(InjectionTokens.FAVORITE_COLORS, favoriteColors);
168
+ provide(InjectionTokens.PAGE_BLOCKS, pageBlocks);
158
169
 
159
170
  return { editor, toolbarRef, wysiwygRef, toolbar };
160
171
  }
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 28 28">
2
+ <path fill="var(--zw-icon-foreground)" fill-rule="evenodd" d="M12 17.5h-2a3.5 3.5 0 1 1 0-7h2V9h-2a5 5 0 0 0 0 10h2v-1.5Zm6-4.5h-8v2h8v-2Zm-2-4h2a5 5 0 0 1 0 10h-2v-1.5h2a3.5 3.5 0 1 0 0-7h-2V9Z" clip-rule="evenodd"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 20 20">
2
+ <path fill="var(--zw-icon-foreground)" fill-rule="evenodd" d="M18.96 10a5 5 0 0 0-5-5h-2v1.5h2a3.49 3.49 0 0 1 1.58 6.61L13.43 11h.53V9h-2.53l-7-7-1.47 1.47L17.49 18l1.47-1.47-2.34-2.34A4.93 4.93 0 0 0 18.96 10Zm-13 1h1.59L5.96 9.41V11ZM3.81 7.26A3.47 3.47 0 0 0 2.46 10a3.5 3.5 0 0 0 3.5 3.5h2V15h-2a5 5 0 0 1-3.21-8.8l1.06 1.06Z" clip-rule="evenodd"/>
3
+ </svg>
@@ -20,7 +20,7 @@ export default {
20
20
  skin: {
21
21
  type: String,
22
22
  required: false,
23
- validator: (skin) => ['toolbar', 'none'].includes(skin),
23
+ validator: (skin) => ['toolbar', 'primary', 'secondary', 'none'].includes(skin),
24
24
  default: 'none'
25
25
  },
26
26
 
@@ -104,6 +104,26 @@ export default {
104
104
  will-change: background-color, color, opacity;
105
105
  }
106
106
 
107
+ .zw-button--primary {
108
+ background-color: rgb(var(--zw-color-green));
109
+ color: rgb(var(--zw-color-white));
110
+ padding: var(--zw-offset-xxs) var(--zw-offset-sm);
111
+ line-height: var(--zw-line-height-md);
112
+ }
113
+
114
+ .zw-button--primary,
115
+ .zw-button--secondary {
116
+ color: rgb(var(--zw-color-white));
117
+ padding: var(--zw-offset-xxs) var(--zw-offset-sm);
118
+ font-weight: 600;
119
+ font-size: var(--zw-font-size-xs);
120
+ }
121
+
122
+ .zw-button--primary:not(:disabled):hover,
123
+ .zw-button--secondary:not(:disabled):hover {
124
+ opacity: 0.9;
125
+ }
126
+
107
127
  .zw-button--toolbar:not(.zw-button--icon) {
108
128
  padding: var(--zw-offset-xxs) var(--zw-offset-xs);
109
129
  }
@@ -0,0 +1,89 @@
1
+ <template>
2
+ <label class="zw-checkbox">
3
+ <input class="zw-checkbox__field" type="checkbox" :checked="value" @change="onCheckedChanged">
4
+ <span class="zw-checkbox__indicator zw-margin-right--xs" />
5
+ <span class="zw-checkbox__label">{{ label }}</span>
6
+ </label>
7
+ </template>
8
+
9
+ <script>
10
+ export default {
11
+ name: 'Checkbox',
12
+
13
+ props: {
14
+ value: {
15
+ type: Boolean,
16
+ required: true
17
+ },
18
+
19
+ label: {
20
+ type: String,
21
+ required: false,
22
+ default: null
23
+ }
24
+ },
25
+
26
+ setup(_, { emit }) {
27
+ const onCheckedChanged = (event) => {
28
+ emit('input', event.target.checked);
29
+ };
30
+
31
+ return { onCheckedChanged };
32
+ }
33
+ };
34
+ </script>
35
+
36
+ <style scoped>
37
+ .zw-checkbox {
38
+ display: inline-flex;
39
+ align-items: center;
40
+ position: relative;
41
+ cursor: pointer;
42
+ padding: var(--zw-offset-xxs) var(--zw-offset-xxs) var(--zw-offset-xxs) 0;
43
+ }
44
+
45
+ .zw-checkbox__field + .zw-checkbox__indicator {
46
+ color: var(--zw-color-n200);
47
+ box-shadow: inset 0 0 0 2px currentColor;
48
+ }
49
+
50
+ .zw-checkbox:hover .zw-checkbox__indicator {
51
+ box-shadow: inset 0 0 0 2px rgb(var(--zw-color-green));
52
+ }
53
+
54
+ .zw-checkbox__field:checked + .zw-checkbox__indicator {
55
+ color: rgb(var(--zw-color-green))
56
+ }
57
+
58
+ .zw-checkbox .zw-checkbox__field:checked + .zw-checkbox__indicator {
59
+ background-color: rgb(var(--zw-color-green));
60
+ }
61
+
62
+ .zw-checkbox .zw-checkbox__indicator::after {
63
+ content: "";
64
+ display: block;
65
+ height: 16px;
66
+ transform: scale(0.6);
67
+ transition: transform 0.2s ease-out;
68
+ width: 16px;
69
+ background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNiAyNiIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiPgogIDxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik0uMyAxNGMtLjItLjItLjMtLjUtLjMtLjdzLjEtLjUuMy0uN2wxLjQtMS40Yy40LS40IDEtLjQgMS40IDBsLjEuMSA1LjUgNS45Yy4yLjIuNS4yLjcgMEwyMi44IDMuM2guMWMuNC0uNCAxLS40IDEuNCAwbDEuNCAxLjRjLjQuNC40IDEgMCAxLjRsLTE2IDE2LjZjLS4yLjItLjQuMy0uNy4zLS4zIDAtLjUtLjEtLjctLjNMLjUgMTQuMy4zIDE0eiIvPgo8L3N2Zz4K");
70
+ background-repeat: no-repeat;
71
+ background-size: 16px;
72
+ background-position: center center;
73
+ }
74
+
75
+ .zw-checkbox__field:not(:checked) + .zw-checkbox__indicator::after {
76
+ transform: scale(0);
77
+ }
78
+
79
+ .zw-checkbox__field {
80
+ position: absolute;
81
+ opacity: 0;
82
+ height: 0;
83
+ width: 0;
84
+ }
85
+
86
+ .zw-checkbox__label {
87
+ font-size: var(--zw-font-size-xs);
88
+ }
89
+ </style>
@@ -11,7 +11,8 @@ export default {
11
11
  props: {
12
12
  fieldId: {
13
13
  type: String,
14
- required: true
14
+ required: false,
15
+ default: null
15
16
  }
16
17
  }
17
18
  };
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <span
2
+ <div
3
3
  class="zw-icon"
4
4
  :class="iconClasses"
5
5
  :style="iconStyles"
@@ -66,7 +66,7 @@ export default {
66
66
 
67
67
  <style scoped>
68
68
  .zw-icon {
69
- display: inline-block;
69
+ display: flex;
70
70
  }
71
71
 
72
72
  .zw-icon--auto-color {
@@ -76,7 +76,6 @@ export default {
76
76
  <style scoped>
77
77
  .zw-modal {
78
78
  border-radius: 2px;
79
- overflow: hidden;
80
79
  box-shadow: 0 0 4px rgba(var(--zw-color-black), 0.3);
81
80
  background-color: rgb(var(--zw-color-n15));
82
81
  max-height: var(--zw-modal-max-height);
@@ -0,0 +1,106 @@
1
+ <template>
2
+ <div class="zw-field">
3
+ <label class="zw-field__label" :for="fieldId" data-test-selector="label">
4
+ {{ label }}
5
+ </label>
6
+
7
+ <input
8
+ class="zw-field__input"
9
+ type="text"
10
+ :value="value"
11
+ :id="fieldId"
12
+ :placeholder="placeholder"
13
+ @input="onInput"
14
+ data-test-selector="input"
15
+ >
16
+
17
+ <p class="zw-field__label--error" v-if="error" data-test-selector="error">
18
+ {{ error }}
19
+ </p>
20
+ </div>
21
+ </template>
22
+
23
+ <script>
24
+ import { computed } from '@vue/composition-api';
25
+
26
+ export default {
27
+ name: 'TextField',
28
+
29
+ props: {
30
+ value: {
31
+ type: [Number, String],
32
+ required: true
33
+ },
34
+
35
+ label: {
36
+ type: String,
37
+ required: true
38
+ },
39
+
40
+ placeholder: {
41
+ type: String,
42
+ required: false,
43
+ default: ''
44
+ },
45
+
46
+ error: {
47
+ type: String,
48
+ required: false,
49
+ default: null
50
+ }
51
+ },
52
+
53
+ setup(props, { emit }) {
54
+ const onInput = (event) => emit('input', event.target.value);
55
+ const fieldId = computed(() => {
56
+ return props.label.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase());
57
+ });
58
+
59
+ return { onInput, fieldId };
60
+ }
61
+ };
62
+ </script>
63
+
64
+ <style lang="scss" scoped>
65
+ .zw-field {
66
+ display: flex;
67
+ flex-direction: column;
68
+ }
69
+
70
+ .zw-field__input {
71
+ --border-color: rgb(var(--zw-color-n60));
72
+ --text-color: rgb(var(--zw-color-n85));
73
+
74
+ border: 1px solid var(--border-color);
75
+ background-color: transparent;
76
+ color: var(--text-color);
77
+ font-size: var(--zw-font-size-xxs);
78
+ outline: none;
79
+ padding: 6px;
80
+ line-height: var(--zw-line-height-xxs);
81
+ }
82
+
83
+ .zw-field__input:hover {
84
+ --border-color: rgb(var(--zw-color-n80));
85
+ --text-color: rgb(var(--zw-color-n85));
86
+ }
87
+
88
+ .zw-field__input:focus,
89
+ .zw-field__input:focus-within {
90
+ --border-color: rgb(var(--zw-color-white));
91
+ --text-color: rgb(var(--zw-color-white));
92
+ }
93
+
94
+ .zw-field__label {
95
+ display: inline-block;
96
+ font-size: var(--zw-font-size-xxs);
97
+ padding-bottom: var(--zw-offset-xxs);
98
+ line-height: var(--zw-line-height-xxs);
99
+ }
100
+
101
+ .zw-field__label--error {
102
+ font-size: var(--zw-font-size-xxs);
103
+ margin: var(--zw-offset-xxs) 0 0;
104
+ color: rgb(var(--zw-color-red));
105
+ }
106
+ </style>
@@ -0,0 +1,57 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import TextField from '../TextField';
3
+
4
+ function createComponent(label, error) {
5
+ return shallowMount(TextField, {
6
+ propsData: {
7
+ value: '',
8
+ label: label ?? '',
9
+ placeholder: '',
10
+ error: error ?? null
11
+ }
12
+ });
13
+ }
14
+
15
+ const SELECTORS = {
16
+ LABEL: '[data-test-selector="label"]',
17
+ INPUT: '[data-test-selector="input"]',
18
+ ERROR: '[data-test-selector="error"]'
19
+ };
20
+
21
+ describe('rendering', () => {
22
+ test('should render label', () => {
23
+ const label = 'Hello world!';
24
+ const wrapper = createComponent(label);
25
+
26
+ expect(wrapper.find(SELECTORS.LABEL).text()).toEqual(label);
27
+ });
28
+
29
+ test('should generate label', () => {
30
+ const wrapper = createComponent('hello world label');
31
+
32
+ expect(wrapper.find(SELECTORS.INPUT).element.id).toBe('helloWorldLabel');
33
+ });
34
+
35
+ test('should render errors', () => {
36
+ const wrapper = createComponent('_', 'some error');
37
+
38
+ expect(wrapper.contains(SELECTORS.ERROR)).toBeTruthy();
39
+ });
40
+
41
+ test('should not render errors', () => {
42
+ const wrapper = createComponent();
43
+
44
+ expect(wrapper.contains(SELECTORS.ERROR)).toBeFalsy();
45
+ });
46
+ });
47
+
48
+ describe('input change handling', () => {
49
+ test('should emit parsed value', () => {
50
+ const wrapper = createComponent();
51
+ const inputComponentWrapper = wrapper.find(SELECTORS.INPUT);
52
+
53
+ inputComponentWrapper.setValue('Hello world!!!');
54
+
55
+ expect(wrapper.emitted('input')).toMatchSnapshot();
56
+ });
57
+ });
@@ -0,0 +1,9 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`input change handling should emit parsed value 1`] = `
4
+ Array [
5
+ Array [
6
+ "Hello world!!!",
7
+ ],
8
+ ]
9
+ `;
@@ -1,3 +1,4 @@
1
+ export { useValidator } from './useValidator';
1
2
  export { useTempValue } from './useTempValue';
2
3
  export { useElementRef } from './useElementRef';
3
4
  export { useNumberValue } from './useNumberValue';
@@ -0,0 +1,19 @@
1
+ import { ref } from '@vue/composition-api';
2
+
3
+ export function useValidator({ validations }) {
4
+ const error = ref(null);
5
+
6
+ function validate(value) {
7
+ for (const validate of validations) {
8
+ const validationError = validate(value);
9
+
10
+ if (validationError) {
11
+ return error.value = validationError;
12
+ }
13
+
14
+ error.value = null;
15
+ }
16
+ }
17
+
18
+ return { error, validate };
19
+ }
@@ -1,12 +1,12 @@
1
1
  <template>
2
2
  <div class="zw-dropdown" ref="dropdownRef">
3
- <DropdownActivator>
3
+ <DropdownActivator :color="color">
4
4
  <template #default="attrs">
5
5
  <slot name="activator" v-bind="attrs" />
6
6
  </template>
7
7
  </DropdownActivator>
8
8
 
9
- <Modal max-height="300" max-width="156" :toggler="toggler" ref="modalRef">
9
+ <Modal max-height="300" :max-width="maxWidth" :toggler="toggler" ref="modalRef">
10
10
  <DropdownMenu :options="options">
11
11
  <template #option="attrs">
12
12
  <slot name="option" v-bind="attrs" />
@@ -40,7 +40,7 @@ export default {
40
40
 
41
41
  props: {
42
42
  value: {
43
- type: String,
43
+ type: [String, Number],
44
44
  required: false,
45
45
  default: null
46
46
  },
@@ -48,6 +48,18 @@ export default {
48
48
  options: {
49
49
  type: Array,
50
50
  required: true
51
+ },
52
+
53
+ maxWidth: {
54
+ type: Number,
55
+ required: false,
56
+ default: 156
57
+ },
58
+
59
+ color: {
60
+ type: String,
61
+ required: false,
62
+ default: 'none'
51
63
  }
52
64
  },
53
65
 
@@ -24,7 +24,7 @@
24
24
  </template>
25
25
 
26
26
  <script>
27
- import { computed, inject } from '@vue/composition-api';
27
+ import { computed, inject, toRef } from '@vue/composition-api';
28
28
  import Button from '../Button';
29
29
  import Icon from '../Icon';
30
30
  import { InjectionTokens } from './injectionTokens';
@@ -42,14 +42,24 @@ export default {
42
42
  event: 'change'
43
43
  },
44
44
 
45
- setup() {
45
+ props: {
46
+ color: {
47
+ type: String,
48
+ required: false,
49
+ default: 'none'
50
+ }
51
+ },
52
+
53
+ setup(props) {
46
54
  const activeOptionManager = inject(InjectionTokens.ACTIVE_MANAGER);
47
55
  const dropdownToggler = inject(InjectionTokens.TOGGLER);
56
+ const color = toRef(props, 'color');
48
57
 
49
58
  const activeOptionTitle = useDropdownEntityTitle(activeOptionManager.activeOption);
50
59
 
51
60
  const dropdownClasses = computed(() => ({
52
- 'zw-dropdown__activator--active': dropdownToggler.isOpened.value
61
+ 'zw-dropdown__activator--active': dropdownToggler.isOpened.value,
62
+ 'zw-dropdown__activator--gray': color.value === 'gray'
53
63
  }));
54
64
 
55
65
  return {
@@ -78,4 +88,10 @@ export default {
78
88
  .zw-dropdown__activator--active .zw-dropdown__activator-arrow {
79
89
  transform: rotateX(180deg);
80
90
  }
91
+
92
+ .zw-dropdown__activator--gray {
93
+ background-color: rgb(var(--zw-color-n20));
94
+ font-size: var(--zw-font-size-xxs);
95
+ color: rgb(var(--zw-color-white));
96
+ }
81
97
  </style>
@@ -6,6 +6,8 @@ export { default as FieldLabel } from './FieldLabel';
6
6
  export { default as Range } from './Range';
7
7
  export { default as NumberField } from './NumberField';
8
8
  export { default as Modal } from './Modal';
9
+ export { default as TextField } from './TextField';
10
+ export { default as Checkbox } from './Checkbox';
9
11
  export { useModalToggler, useElementRef } from './composables';
10
12
  export * from './dropdown';
11
13
  export * from './colorPicker';
@@ -1,11 +1,11 @@
1
1
  <template>
2
- <transition name="zw-toolbar-" :duration="150">
3
- <keep-alive>
2
+ <keep-alive>
3
+ <transition name="zw-toolbar-" duration="150">
4
4
  <div class="zw-toolbar" :style="toolbarStyles" ref="toolbarRef" v-if="isVisible">
5
5
  <component :is="toolbarComponent" />
6
6
  </div>
7
- </keep-alive>
8
- </transition>
7
+ </transition>
8
+ </keep-alive>
9
9
  </template>
10
10
 
11
11
  <script>
@@ -82,7 +82,7 @@ export default {
82
82
 
83
83
  .zw-toolbar--enter-active,
84
84
  .zw-toolbar--leave-active {
85
- transition: opacity 0.15s ease-out;
85
+ transition: opacity 150ms ease-out;
86
86
  }
87
87
 
88
88
  .zw-toolbar--leave-active {