@zipify/wysiwyg 1.0.0-dev.16 → 1.0.0-dev.19

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 (100) hide show
  1. package/.github/dependabot.yaml +1 -0
  2. package/dist/wysiwyg.css +293 -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 +16 -6
  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 +3 -1
  23. package/lib/components/toolbar/Toolbar.vue +48 -8
  24. package/lib/components/toolbar/ToolbarFull.vue +10 -2
  25. package/lib/components/toolbar/__tests__/Toolbar.test.js +6 -0
  26. package/lib/components/toolbar/controls/FontSizeControl.vue +7 -0
  27. package/lib/components/toolbar/controls/UnderlineControl.vue +2 -2
  28. package/lib/components/toolbar/controls/__tests__/UnderlineControl.test.js +4 -0
  29. package/lib/components/toolbar/controls/index.js +1 -0
  30. package/lib/components/toolbar/controls/link/LinkControl.vue +152 -0
  31. package/lib/components/toolbar/controls/link/LinkControlApply.vue +35 -0
  32. package/lib/components/toolbar/controls/link/LinkControlHeader.vue +67 -0
  33. package/lib/components/toolbar/controls/link/composables/index.js +1 -0
  34. package/lib/components/toolbar/controls/link/composables/useLink.js +61 -0
  35. package/lib/components/toolbar/controls/link/destination/LinkControlDestination.vue +103 -0
  36. package/lib/components/toolbar/controls/link/destination/LinkControlPageBlock.vue +54 -0
  37. package/lib/components/toolbar/controls/link/destination/LinkControlUrl.vue +52 -0
  38. package/lib/components/toolbar/controls/link/destination/index.js +1 -0
  39. package/lib/components/toolbar/controls/link/index.js +1 -0
  40. package/lib/composables/__tests__/useEditor.test.js +2 -2
  41. package/lib/composables/useEditor.js +4 -5
  42. package/lib/composables/useToolbar.js +15 -19
  43. package/lib/enums/LinkDestinations.js +4 -0
  44. package/lib/enums/LinkTargets.js +4 -0
  45. package/lib/enums/TextSettings.js +3 -1
  46. package/lib/enums/index.js +2 -0
  47. package/lib/extensions/Alignment.js +18 -6
  48. package/lib/extensions/BackgroundColor.js +14 -6
  49. package/lib/extensions/FontColor.js +14 -6
  50. package/lib/extensions/FontFamily.js +25 -8
  51. package/lib/extensions/FontSize.js +26 -13
  52. package/lib/extensions/FontStyle.js +23 -13
  53. package/lib/extensions/FontWeight.js +22 -14
  54. package/lib/extensions/LineHeight.js +11 -3
  55. package/lib/extensions/Link.js +101 -0
  56. package/lib/extensions/StylePreset.js +4 -2
  57. package/lib/extensions/TextDecoration.js +27 -12
  58. package/lib/extensions/__tests__/Alignment.test.js +11 -5
  59. package/lib/extensions/__tests__/BackgroundColor.test.js +11 -5
  60. package/lib/extensions/__tests__/CaseStyle.test.js +3 -5
  61. package/lib/extensions/__tests__/FontColor.test.js +11 -5
  62. package/lib/extensions/__tests__/FontFamily.test.js +32 -7
  63. package/lib/extensions/__tests__/FontSize.test.js +11 -5
  64. package/lib/extensions/__tests__/FontStyle.test.js +11 -5
  65. package/lib/extensions/__tests__/FontWeight.test.js +11 -5
  66. package/lib/extensions/__tests__/LineHeight.test.js +11 -5
  67. package/lib/extensions/__tests__/StylePreset.test.js +70 -6
  68. package/lib/extensions/__tests__/TextDecoration.test.js +27 -5
  69. package/lib/extensions/__tests__/__snapshots__/Alignment.test.js.snap +26 -2
  70. package/lib/extensions/__tests__/__snapshots__/BackgroundColor.test.js.snap +30 -1
  71. package/lib/extensions/__tests__/__snapshots__/FontColor.test.js.snap +18 -1
  72. package/lib/extensions/__tests__/__snapshots__/FontFamily.test.js.snap +88 -1
  73. package/lib/extensions/__tests__/__snapshots__/FontSize.test.js.snap +33 -2
  74. package/lib/extensions/__tests__/__snapshots__/FontStyle.test.js.snap +25 -4
  75. package/lib/extensions/__tests__/__snapshots__/FontWeight.test.js.snap +30 -1
  76. package/lib/extensions/__tests__/__snapshots__/LineHeight.test.js.snap +26 -2
  77. package/lib/extensions/__tests__/__snapshots__/StylePreset.test.js.snap +6 -2
  78. package/lib/extensions/__tests__/__snapshots__/TextDecoration.test.js.snap +93 -3
  79. package/lib/extensions/core/CopyPasteProcessor.js +10 -0
  80. package/lib/extensions/core/TextProcessor.js +10 -0
  81. package/lib/extensions/core/__tests__/NodeProcessor.test.js +3 -5
  82. package/lib/extensions/core/__tests__/SelectionProcessor.test.js +3 -5
  83. package/lib/extensions/core/__tests__/TextProcessor.test.js +138 -12
  84. package/lib/extensions/core/__tests__/__snapshots__/TextProcessor.test.js.snap +26 -0
  85. package/lib/extensions/core/index.js +11 -2
  86. package/lib/extensions/core/plugins/PastePlugin.js +48 -0
  87. package/lib/extensions/core/plugins/ProseMirrorPlugin.js +20 -0
  88. package/lib/extensions/core/plugins/index.js +1 -0
  89. package/lib/extensions/index.js +41 -34
  90. package/lib/extensions/list/__tests__/List.test.js +3 -5
  91. package/lib/extensions/list/__tests__/__snapshots__/List.test.js.snap +45 -15
  92. package/lib/injectionTokens.js +2 -1
  93. package/lib/services/ContentNormalizer.js +62 -20
  94. package/lib/services/__tests__/ContentNormalizer.test.js +40 -7
  95. package/lib/styles/content.css +96 -9
  96. package/lib/styles/helpers/offsets.css +16 -0
  97. package/lib/styles/variables.css +6 -0
  98. package/lib/utils/__tests__/__snapshots__/renderInlineSetting.test.js.snap +4 -4
  99. package/lib/utils/renderInlineSetting.js +1 -1
  100. 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
@@ -1,11 +1,11 @@
1
1
  <template>
2
2
  <div class="zw-wysiwyg" ref="wysiwygRef">
3
3
  <Toolbar
4
- v-show="active"
5
- :editor="editor"
4
+ :toolbar="toolbar"
6
5
  :device="device"
7
6
  ref="toolbarRef"
8
7
  />
8
+
9
9
  <EditorContent :editor="editor" />
10
10
  </div>
11
11
  </template>
@@ -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,13 +114,13 @@ 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);
115
121
 
116
122
  const toolbar = useToolbar({
117
123
  wrapperRef: wysiwygRef,
118
- popperRef: toolbarRef,
119
124
  isActiveRef: toRef(props, 'active'),
120
125
  offsets: props.toolbarOffsets
121
126
  });
@@ -125,11 +130,13 @@ export default {
125
130
  toolbar.update();
126
131
  }
127
132
 
133
+ const pageBlocks = toRef(props, 'pageBlocks');
134
+
128
135
  const editor = useEditor({
129
136
  content: toRef(props, 'value'),
130
137
  onChange: (content) => onChange(content),
131
138
 
132
- extensions: getExtensions({
139
+ extensions: buildExtensions({
133
140
  fonts,
134
141
  minFontSize: MIN_FONT_SIZE,
135
142
  maxFontSize: MAX_FONT_SIZE,
@@ -138,7 +145,9 @@ export default {
138
145
  makePresetVariable: props.makePresetVariable,
139
146
  basePresetClass: props.basePresetClass,
140
147
  baseListClass: props.baseListClass,
141
- deviceRef: toRef(props, 'device')
148
+ linkPreset: presets.value.find((preset) => preset.id === 'link'),
149
+ deviceRef: toRef(props, 'device'),
150
+ pageBlocks
142
151
  })
143
152
  });
144
153
 
@@ -156,6 +165,7 @@ export default {
156
165
  provide(InjectionTokens.FONT_SIZES, fontSizes);
157
166
  provide(InjectionTokens.LOCAL_STORAGE, new Storage(localStorage));
158
167
  provide(InjectionTokens.FAVORITE_COLORS, favoriteColors);
168
+ provide(InjectionTokens.PAGE_BLOCKS, pageBlocks);
159
169
 
160
170
  return { editor, toolbarRef, wysiwygRef, toolbar };
161
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 { useModalToggler } from './composables';
9
+ export { default as TextField } from './TextField';
10
+ export { default as Checkbox } from './Checkbox';
11
+ export { useModalToggler, useElementRef } from './composables';
10
12
  export * from './dropdown';
11
13
  export * from './colorPicker';