@rancher/shell 0.3.17 → 0.3.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.
- package/assets/translations/en-us.yaml +8 -4
- package/assets/translations/zh-hans.yaml +64 -8
- package/components/AsyncButton.vue +1 -1
- package/components/Inactivity.vue +10 -0
- package/components/LazyImage.vue +2 -2
- package/components/PromptRestore.vue +8 -6
- package/components/ResourceDetail/Masthead.vue +1 -1
- package/components/ResourceDetail/index.vue +4 -2
- package/components/__tests__/PromptRestore.test.ts +142 -0
- package/components/auth/AzureWarning.vue +1 -1
- package/components/auth/RoleDetailEdit.vue +2 -0
- package/components/fleet/FleetResources.vue +3 -64
- package/components/form/FileImageSelector.vue +9 -0
- package/components/form/FileSelector.vue +2 -1
- package/components/form/MatchExpressions.vue +1 -3
- package/components/form/__tests__/FileImageSelector.test.ts +42 -0
- package/components/form/__tests__/FileSelector.test.ts +76 -0
- package/components/formatter/ClusterProvider.vue +3 -1
- package/components/formatter/__tests__/ClusterProvider.test.ts +24 -0
- package/components/nav/WindowManager/ContainerShell.vue +60 -36
- package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +561 -0
- package/config/labels-annotations.js +2 -1
- package/config/persistentVolume.ts +108 -0
- package/config/product/manager.js +5 -1
- package/config/types.js +2 -0
- package/core/plugin-helpers.js +19 -3
- package/core/types.ts +4 -0
- package/detail/fleet.cattle.io.gitrepo.vue +10 -2
- package/detail/pod.vue +36 -3
- package/detail/workload/index.vue +40 -9
- package/dialog/DiagnosticTimingsDialog.vue +1 -0
- package/edit/__tests__/ui.cattle.io.navlink.test.ts +110 -0
- package/edit/fleet.cattle.io.clustergroup.vue +14 -3
- package/edit/persistentvolume/__tests__/persistentvolume.test.ts +82 -0
- package/edit/persistentvolume/index.vue +2 -1
- package/edit/persistentvolume/plugins/csi.vue +3 -1
- package/edit/persistentvolume/plugins/longhorn.vue +12 -12
- package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +15 -11
- package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +5 -1
- package/edit/storage.k8s.io.storageclass/index.vue +1 -2
- package/edit/ui.cattle.io.navlink.vue +213 -186
- package/layouts/default.vue +1 -1
- package/list/group.principal.vue +1 -1
- package/middleware/authenticated.js +12 -4
- package/mixins/create-edit-view/impl.js +2 -2
- package/models/chart.js +1 -1
- package/models/etcdbackup.js +2 -1
- package/models/fleet.cattle.io.cluster.js +33 -4
- package/models/fleet.cattle.io.gitrepo.js +112 -38
- package/models/management.cattle.io.cluster.js +13 -3
- package/models/management.cattle.io.kontainerdriver.js +14 -0
- package/models/persistentvolume.js +2 -111
- package/models/pod.js +30 -0
- package/models/rke.cattle.io.etcdsnapshot.js +10 -7
- package/package.json +1 -1
- package/pages/c/_cluster/apps/charts/install.vue +74 -25
- package/pages/c/_cluster/auth/group.principal/assign-edit.vue +1 -1
- package/pages/c/_cluster/auth/roles/index.vue +1 -1
- package/pages/c/_cluster/explorer/index.vue +1 -1
- package/pages/c/_cluster/manager/cloudCredential/_id.vue +0 -1
- package/pages/c/_cluster/manager/cloudCredential/create.vue +0 -1
- package/pages/c/_cluster/settings/brand.vue +11 -8
- package/pages/c/_cluster/uiplugins/index.vue +9 -4
- package/pages/diagnostic.vue +5 -3
- package/pages/home.vue +1 -1
- package/plugins/dashboard-store/__tests__/actions.spec.ts +165 -0
- package/plugins/dashboard-store/__tests__/getters.spec.ts +100 -0
- package/plugins/dashboard-store/__tests__/{mutations.spec.js → mutations.spec.ts} +2 -2
- package/plugins/dashboard-store/actions.js +1 -1
- package/plugins/dashboard-store/resource-class.js +4 -0
- package/plugins/steve/__tests__/getters.spec.ts +93 -0
- package/plugins/steve/getters.js +21 -1
- package/plugins/steve/subscribe.js +1 -3
- package/rancher-components/components/BadgeState/BadgeState.spec.ts +12 -0
- package/rancher-components/components/BadgeState/BadgeState.vue +111 -0
- package/rancher-components/components/BadgeState/index.ts +1 -0
- package/rancher-components/components/Banner/Banner.test.ts +63 -0
- package/rancher-components/components/Banner/Banner.vue +244 -0
- package/rancher-components/components/Banner/index.ts +1 -0
- package/rancher-components/components/Card/Card.test.ts +37 -0
- package/rancher-components/components/Card/Card.vue +167 -0
- package/rancher-components/components/Card/index.ts +1 -0
- package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +68 -0
- package/rancher-components/components/Form/Checkbox/Checkbox.vue +420 -0
- package/rancher-components/components/Form/Checkbox/index.ts +1 -0
- package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +23 -0
- package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +355 -0
- package/rancher-components/components/Form/LabeledInput/index.ts +1 -0
- package/rancher-components/components/Form/Radio/RadioButton.test.ts +31 -0
- package/rancher-components/components/Form/Radio/RadioButton.vue +287 -0
- package/rancher-components/components/Form/Radio/RadioGroup.vue +254 -0
- package/rancher-components/components/Form/Radio/index.ts +2 -0
- package/rancher-components/components/Form/TextArea/TextAreaAutoGrow.vue +170 -0
- package/rancher-components/components/Form/TextArea/index.ts +1 -0
- package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +94 -0
- package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.vue +149 -0
- package/rancher-components/components/Form/ToggleSwitch/index.ts +1 -0
- package/rancher-components/components/Form/index.ts +5 -0
- package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +151 -0
- package/rancher-components/components/LabeledTooltip/index.ts +1 -0
- package/rancher-components/components/StringList/StringList.test.ts +484 -0
- package/rancher-components/components/StringList/StringList.vue +611 -0
- package/rancher-components/components/StringList/index.ts +1 -0
- package/scripts/extension/publish +54 -14
- package/scripts/typegen.sh +10 -2
- package/store/index.js +1 -3
- package/store/store-types.js +2 -0
- package/types/api.d.ts +1 -0
- package/types/fleet.d.ts +1 -0
- package/types/shell/index.d.ts +696 -2
- package/types/userPreferences.d.ts +1 -1
- package/utils/__mocks__/socket.js +21 -0
- package/utils/grafana.js +23 -11
- package/utils/selector.js +2 -1
- package/utils/socket.js +1 -0
- package/utils/validators/formRules/index.ts +3 -3
- package/plugins/steve/urloptions.js +0 -47
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Vue, { PropType } from 'vue';
|
|
3
|
+
import { _VIEW } from '@shell/config/query-params';
|
|
4
|
+
import RadioButton from '@components/Form/Radio/RadioButton.vue';
|
|
5
|
+
|
|
6
|
+
interface Option {
|
|
7
|
+
value: unknown,
|
|
8
|
+
label: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default Vue.extend({
|
|
12
|
+
components: { RadioButton },
|
|
13
|
+
props: {
|
|
14
|
+
/**
|
|
15
|
+
* Name for the checkbox grouping, must be unique on page.
|
|
16
|
+
*/
|
|
17
|
+
name: {
|
|
18
|
+
type: String,
|
|
19
|
+
required: true
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Options can be an array of {label, value}, or just values.
|
|
24
|
+
*/
|
|
25
|
+
options: {
|
|
26
|
+
type: Array as PropType<Option[] | string[]>,
|
|
27
|
+
required: true
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* If options are just values, then labels can be a corresponding display
|
|
32
|
+
* value.
|
|
33
|
+
*/
|
|
34
|
+
labels: {
|
|
35
|
+
type: Array as PropType<string[]>,
|
|
36
|
+
default: null
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The selected value.
|
|
41
|
+
*/
|
|
42
|
+
value: {
|
|
43
|
+
type: [Boolean, String, Object],
|
|
44
|
+
default: null
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Disable the radio group.
|
|
49
|
+
*/
|
|
50
|
+
disabled: {
|
|
51
|
+
type: Boolean,
|
|
52
|
+
default: false
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The radio group editing mode.
|
|
57
|
+
* @values _EDIT, _VIEW
|
|
58
|
+
*/
|
|
59
|
+
mode: {
|
|
60
|
+
type: String,
|
|
61
|
+
default: 'edit'
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Label for above the radios.
|
|
66
|
+
*/
|
|
67
|
+
label: {
|
|
68
|
+
type: String,
|
|
69
|
+
default: null
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The i18n key to use for the radio group label.
|
|
74
|
+
*/
|
|
75
|
+
labelKey: {
|
|
76
|
+
type: String,
|
|
77
|
+
default: null
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Radio group tooltip.
|
|
82
|
+
*/
|
|
83
|
+
tooltip: {
|
|
84
|
+
type: [String, Object],
|
|
85
|
+
default: null
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The i18n key to use for the radio group tooltip.
|
|
90
|
+
*/
|
|
91
|
+
tooltipKey: {
|
|
92
|
+
type: String,
|
|
93
|
+
default: null
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Show radio buttons in column or row.
|
|
98
|
+
*/
|
|
99
|
+
row: {
|
|
100
|
+
type: Boolean,
|
|
101
|
+
default: false
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
computed: {
|
|
106
|
+
/**
|
|
107
|
+
* Creates a collection of Options from the provided props.
|
|
108
|
+
*/
|
|
109
|
+
normalizedOptions(): Option[] {
|
|
110
|
+
const out: Option[] = [];
|
|
111
|
+
|
|
112
|
+
for (let i = 0; i < this.options.length; i++) {
|
|
113
|
+
const opt = this.options[i];
|
|
114
|
+
|
|
115
|
+
if (typeof opt === 'object' && opt) {
|
|
116
|
+
out.push(opt);
|
|
117
|
+
} else if (this.labels) {
|
|
118
|
+
out.push({
|
|
119
|
+
label: this.labels[i],
|
|
120
|
+
value: opt
|
|
121
|
+
});
|
|
122
|
+
} else {
|
|
123
|
+
out.push({
|
|
124
|
+
label: opt,
|
|
125
|
+
value: opt
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return out;
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Determines the view mode for the radio group.
|
|
135
|
+
*/
|
|
136
|
+
isView(): boolean {
|
|
137
|
+
return this.mode === _VIEW;
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Determines if the radio group is disabled.
|
|
142
|
+
*/
|
|
143
|
+
isDisabled(): boolean {
|
|
144
|
+
return (this.disabled || this.isView);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
methods: {
|
|
149
|
+
/**
|
|
150
|
+
* Keyboard left/right event listener to select next/previous option. Emits
|
|
151
|
+
* the input event.
|
|
152
|
+
*/
|
|
153
|
+
clickNext(direction: number): void {
|
|
154
|
+
const opts = this.normalizedOptions;
|
|
155
|
+
const selected = opts.find((x) => x.value === this.value);
|
|
156
|
+
let newIndex = (selected ? opts.indexOf(selected) : -1) + direction;
|
|
157
|
+
|
|
158
|
+
if (newIndex >= opts.length) {
|
|
159
|
+
newIndex = opts.length - 1;
|
|
160
|
+
} else if (newIndex < 0) {
|
|
161
|
+
newIndex = 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.$emit('input', opts[newIndex].value);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
</script>
|
|
169
|
+
|
|
170
|
+
<template>
|
|
171
|
+
<div>
|
|
172
|
+
<div
|
|
173
|
+
v-if="label || labelKey || tooltip || tooltipKey || $slots.label"
|
|
174
|
+
class="radio-group label"
|
|
175
|
+
>
|
|
176
|
+
<slot name="label">
|
|
177
|
+
<h3>
|
|
178
|
+
<t
|
|
179
|
+
v-if="labelKey"
|
|
180
|
+
:k="labelKey"
|
|
181
|
+
/>
|
|
182
|
+
<template v-else-if="label">
|
|
183
|
+
{{ label }}
|
|
184
|
+
</template>
|
|
185
|
+
<i
|
|
186
|
+
v-if="tooltipKey"
|
|
187
|
+
v-clean-tooltip="t(tooltipKey)"
|
|
188
|
+
class="icon icon-info icon-lg"
|
|
189
|
+
/>
|
|
190
|
+
<i
|
|
191
|
+
v-else-if="tooltip"
|
|
192
|
+
v-clean-tooltip="tooltip"
|
|
193
|
+
class="icon icon-info icon-lg"
|
|
194
|
+
/>
|
|
195
|
+
</h3>
|
|
196
|
+
</slot>
|
|
197
|
+
</div>
|
|
198
|
+
<div
|
|
199
|
+
class="radio-group"
|
|
200
|
+
:class="{'row':row}"
|
|
201
|
+
tabindex="0"
|
|
202
|
+
@keyup.down.stop="clickNext(1)"
|
|
203
|
+
@keyup.up.stop="clickNext(-1)"
|
|
204
|
+
>
|
|
205
|
+
<div
|
|
206
|
+
v-for="(option, i) in normalizedOptions"
|
|
207
|
+
:key="name+'-'+i"
|
|
208
|
+
>
|
|
209
|
+
<slot
|
|
210
|
+
:listeners="$listeners"
|
|
211
|
+
:option="option"
|
|
212
|
+
:is-disabled="isDisabled"
|
|
213
|
+
:name="i"
|
|
214
|
+
>
|
|
215
|
+
<RadioButton
|
|
216
|
+
:key="name+'-'+i"
|
|
217
|
+
:name="name"
|
|
218
|
+
:value="value"
|
|
219
|
+
:label="option.label"
|
|
220
|
+
:description="option.description"
|
|
221
|
+
:val="option.value"
|
|
222
|
+
:disabled="isDisabled"
|
|
223
|
+
:mode="mode"
|
|
224
|
+
v-on="$listeners"
|
|
225
|
+
/>
|
|
226
|
+
</slot>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</template>
|
|
231
|
+
|
|
232
|
+
<style lang='scss'>
|
|
233
|
+
.radio-group {
|
|
234
|
+
&:focus {
|
|
235
|
+
border:none;
|
|
236
|
+
outline:none;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
h3 {
|
|
240
|
+
position: relative;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
&.row {
|
|
244
|
+
display: flex;
|
|
245
|
+
.radio-container {
|
|
246
|
+
margin-right: 10px;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.label{
|
|
251
|
+
font-size: 14px !important;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
</style>
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Vue from 'vue';
|
|
3
|
+
import debounce from 'lodash/debounce';
|
|
4
|
+
import { _EDIT, _VIEW } from '@shell/config/query-params';
|
|
5
|
+
|
|
6
|
+
declare module 'vue/types/vue' {
|
|
7
|
+
/* eslint-disable no-unused-vars */
|
|
8
|
+
interface Vue {
|
|
9
|
+
queueResize(): void;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default Vue.extend({
|
|
14
|
+
inheritAttrs: false,
|
|
15
|
+
|
|
16
|
+
props: {
|
|
17
|
+
/**
|
|
18
|
+
* Sets the edit mode for Text Area.
|
|
19
|
+
* @values _EDIT, _VIEW
|
|
20
|
+
*/
|
|
21
|
+
mode: {
|
|
22
|
+
type: String,
|
|
23
|
+
default: _EDIT
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Sets the Minimum height for Text Area. Prevents the height from becoming
|
|
28
|
+
* smaller than the value specified in minHeight.
|
|
29
|
+
*/
|
|
30
|
+
minHeight: {
|
|
31
|
+
type: Number,
|
|
32
|
+
default: 25
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Sets the maximum height for Text Area. Prevents the height from becoming
|
|
37
|
+
* larger than the value specified in maxHeight.
|
|
38
|
+
*/
|
|
39
|
+
maxHeight: {
|
|
40
|
+
type: Number,
|
|
41
|
+
default: 200
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Text that appears in the Text Area when it has no value set.
|
|
46
|
+
*/
|
|
47
|
+
placeholder: {
|
|
48
|
+
type: String,
|
|
49
|
+
default: ''
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Specifies whether Text Area is subject to spell checking by the
|
|
54
|
+
* underlying browser/OS.
|
|
55
|
+
*/
|
|
56
|
+
spellcheck: {
|
|
57
|
+
type: Boolean,
|
|
58
|
+
default: true
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Disables the Text Area.
|
|
63
|
+
*/
|
|
64
|
+
disabled: {
|
|
65
|
+
type: Boolean,
|
|
66
|
+
default: false
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
data() {
|
|
71
|
+
return {
|
|
72
|
+
curHeight: this.minHeight,
|
|
73
|
+
overflow: 'hidden'
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
computed: {
|
|
78
|
+
/**
|
|
79
|
+
* Determines if the Text Area should be disabled.
|
|
80
|
+
*/
|
|
81
|
+
isDisabled(): boolean {
|
|
82
|
+
return this.disabled || this.mode === _VIEW;
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Sets the height to one-line for SSR pageload so that it's already right
|
|
87
|
+
* (unless the input is long)
|
|
88
|
+
*/
|
|
89
|
+
style(): string {
|
|
90
|
+
return `height: ${ this.curHeight }px; overflow: ${ this.overflow };`;
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
watch: {
|
|
95
|
+
$attrs: {
|
|
96
|
+
deep: true,
|
|
97
|
+
handler() {
|
|
98
|
+
this.queueResize();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
created() {
|
|
104
|
+
this.queueResize = debounce(this.autoSize, 100);
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
mounted() {
|
|
108
|
+
(this.$refs.ta as HTMLElement).style.height = `${ this.curHeight }px`;
|
|
109
|
+
this.$nextTick(() => {
|
|
110
|
+
this.autoSize();
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
methods: {
|
|
115
|
+
/**
|
|
116
|
+
* Emits the input event and resizes the Text Area.
|
|
117
|
+
*/
|
|
118
|
+
onInput(val: string): void {
|
|
119
|
+
this.$emit('input', val);
|
|
120
|
+
this.queueResize();
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Gives focus to the Text Area.
|
|
125
|
+
*/
|
|
126
|
+
focus(): void {
|
|
127
|
+
(this.$refs?.ta as HTMLElement).focus();
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Sets the overflowY and height of the Text Area based on the content
|
|
132
|
+
* entered (calculated via scroll height).
|
|
133
|
+
*/
|
|
134
|
+
autoSize(): void {
|
|
135
|
+
const el = this.$refs.ta as HTMLElement;
|
|
136
|
+
|
|
137
|
+
if (!el) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
el.style.height = '1px';
|
|
142
|
+
|
|
143
|
+
const border = parseInt(getComputedStyle(el).getPropertyValue('borderTopWidth'), 10) || 0 + parseInt(getComputedStyle(el).getPropertyValue('borderBottomWidth'), 10) || 0;
|
|
144
|
+
const neu = Math.max(this.minHeight, Math.min(el.scrollHeight + border, this.maxHeight));
|
|
145
|
+
|
|
146
|
+
el.style.overflowY = el.scrollHeight > neu ? 'auto' : 'hidden';
|
|
147
|
+
el.style.height = `${ neu }px`;
|
|
148
|
+
|
|
149
|
+
this.curHeight = neu;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
</script>
|
|
154
|
+
|
|
155
|
+
<template>
|
|
156
|
+
<textarea
|
|
157
|
+
ref="ta"
|
|
158
|
+
data-testid="text-area-auto-grow"
|
|
159
|
+
:disabled="isDisabled"
|
|
160
|
+
:style="style"
|
|
161
|
+
:placeholder="placeholder"
|
|
162
|
+
class="no-resize no-ease"
|
|
163
|
+
v-bind="$attrs"
|
|
164
|
+
:spellcheck="spellcheck"
|
|
165
|
+
@paste="$emit('paste', $event)"
|
|
166
|
+
@input="onInput($event.target.value)"
|
|
167
|
+
@focus="$emit('focus', $event)"
|
|
168
|
+
@blur="$emit('blur', $event)"
|
|
169
|
+
/>
|
|
170
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as TextAreaAutoGrow } from './TextAreaAutoGrow.vue';
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { shallowMount, Wrapper } from '@vue/test-utils';
|
|
2
|
+
import { ToggleSwitch } from './index';
|
|
3
|
+
|
|
4
|
+
describe('toggleSwitch.vue', () => {
|
|
5
|
+
it('renders falsy by default', () => {
|
|
6
|
+
const wrapper = shallowMount(ToggleSwitch);
|
|
7
|
+
|
|
8
|
+
const toggleInput = wrapper.find('input[type="checkbox"]').element as HTMLInputElement;
|
|
9
|
+
|
|
10
|
+
expect(toggleInput.checked).toBeFalsy();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('renders a true value', () => {
|
|
14
|
+
const wrapper = shallowMount(
|
|
15
|
+
ToggleSwitch,
|
|
16
|
+
{ propsData: { value: true } });
|
|
17
|
+
|
|
18
|
+
const toggleInput = wrapper.find('input[type="checkbox"]').element as HTMLInputElement;
|
|
19
|
+
|
|
20
|
+
expect(toggleInput.checked).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('updates from falsy to truthy when props change', async() => {
|
|
24
|
+
const wrapper = shallowMount(ToggleSwitch);
|
|
25
|
+
|
|
26
|
+
const toggleInput = wrapper.find('input[type="checkbox"]').element as HTMLInputElement;
|
|
27
|
+
|
|
28
|
+
expect(toggleInput.checked).toBe(false);
|
|
29
|
+
|
|
30
|
+
await wrapper.setProps({ value: true });
|
|
31
|
+
|
|
32
|
+
expect(toggleInput.checked).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('emits an input event with a true value', async() => {
|
|
36
|
+
const wrapper: Wrapper<InstanceType<typeof ToggleSwitch>> = shallowMount(ToggleSwitch);
|
|
37
|
+
|
|
38
|
+
wrapper.vm.toggle(true);
|
|
39
|
+
|
|
40
|
+
await wrapper.vm.$nextTick();
|
|
41
|
+
|
|
42
|
+
expect(wrapper.emitted().input?.length).toBe(1);
|
|
43
|
+
expect(wrapper.emitted().input?.[0][0]).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('emits an input event with a false value', async() => {
|
|
47
|
+
const wrapper: Wrapper<InstanceType<typeof ToggleSwitch>> = shallowMount(
|
|
48
|
+
ToggleSwitch,
|
|
49
|
+
{ propsData: { value: true } }
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
wrapper.vm.toggle(false);
|
|
53
|
+
|
|
54
|
+
await wrapper.vm.$nextTick();
|
|
55
|
+
|
|
56
|
+
expect(wrapper.emitted().input?.length).toBe(1);
|
|
57
|
+
expect(wrapper.emitted().input?.[0][0]).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('emits an input event with a custom onValue', async() => {
|
|
61
|
+
const onValue = 'THE TRUTH';
|
|
62
|
+
|
|
63
|
+
const wrapper: Wrapper<InstanceType<typeof ToggleSwitch>> = shallowMount(
|
|
64
|
+
ToggleSwitch,
|
|
65
|
+
{ propsData: { onValue } });
|
|
66
|
+
|
|
67
|
+
wrapper.vm.toggle(true);
|
|
68
|
+
|
|
69
|
+
await wrapper.vm.$nextTick();
|
|
70
|
+
|
|
71
|
+
expect(wrapper.emitted().input?.length).toBe(1);
|
|
72
|
+
expect(wrapper.emitted().input?.[0][0]).toBe(onValue);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('emits an input event with a custom offValue', async() => {
|
|
76
|
+
const offValue = 'NOT THE TRUTH';
|
|
77
|
+
|
|
78
|
+
const wrapper: Wrapper<InstanceType<typeof ToggleSwitch>> = shallowMount(
|
|
79
|
+
ToggleSwitch,
|
|
80
|
+
{
|
|
81
|
+
propsData: {
|
|
82
|
+
value: true,
|
|
83
|
+
offValue,
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
wrapper.vm.toggle(false);
|
|
88
|
+
|
|
89
|
+
await wrapper.vm.$nextTick();
|
|
90
|
+
|
|
91
|
+
expect(wrapper.emitted().input?.length).toBe(1);
|
|
92
|
+
expect(wrapper.emitted().input?.[0][0]).toBe(offValue);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Vue from 'vue';
|
|
3
|
+
export default Vue.extend({
|
|
4
|
+
props: {
|
|
5
|
+
value: {
|
|
6
|
+
type: [Boolean, String, Number],
|
|
7
|
+
default: false
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
offValue: {
|
|
11
|
+
type: [Boolean, String, Number],
|
|
12
|
+
default: false,
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
onValue: {
|
|
16
|
+
type: [Boolean, String, Number],
|
|
17
|
+
default: true,
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
offLabel: {
|
|
21
|
+
type: String,
|
|
22
|
+
default: '',
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
onLabel: {
|
|
26
|
+
type: String,
|
|
27
|
+
default: '',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
data() {
|
|
31
|
+
return { state: false as boolean | string | number };
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
watch: {
|
|
35
|
+
value: {
|
|
36
|
+
handler() {
|
|
37
|
+
this.state = this.value === this.onValue;
|
|
38
|
+
},
|
|
39
|
+
immediate: true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
methods: {
|
|
44
|
+
toggle(neu: boolean | string | number) {
|
|
45
|
+
this.state = neu === null ? !this.state : neu;
|
|
46
|
+
this.$emit('input', this.state ? this.onValue : this.offValue);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<span class="toggle-container">
|
|
54
|
+
<span
|
|
55
|
+
class="label no-select hand"
|
|
56
|
+
:class="{ active: !state}"
|
|
57
|
+
@click="toggle(false)"
|
|
58
|
+
>{{ offLabel }}</span>
|
|
59
|
+
<label class="switch hand">
|
|
60
|
+
<input
|
|
61
|
+
type="checkbox"
|
|
62
|
+
:checked="state"
|
|
63
|
+
@input="toggle(null)"
|
|
64
|
+
>
|
|
65
|
+
<span class="slider round" />
|
|
66
|
+
</label>
|
|
67
|
+
<span
|
|
68
|
+
class="label no-select hand"
|
|
69
|
+
:class="{ active: state}"
|
|
70
|
+
@click="toggle(true)"
|
|
71
|
+
>{{ onLabel }}</span>
|
|
72
|
+
</span>
|
|
73
|
+
</template>
|
|
74
|
+
|
|
75
|
+
<style lang="scss" scoped>
|
|
76
|
+
$toggle-height: 16px;
|
|
77
|
+
|
|
78
|
+
.toggle-container {
|
|
79
|
+
align-items: center;
|
|
80
|
+
display: flex;
|
|
81
|
+
|
|
82
|
+
span:first-child {
|
|
83
|
+
padding-right: 6px;
|
|
84
|
+
}
|
|
85
|
+
span:last-child {
|
|
86
|
+
padding-left: 6px;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/* The switch - the box around the slider */
|
|
90
|
+
.switch {
|
|
91
|
+
position: relative;
|
|
92
|
+
display: inline-block;
|
|
93
|
+
width: 48px;
|
|
94
|
+
height: $toggle-height + 8px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Hide default HTML checkbox */
|
|
98
|
+
.switch input {
|
|
99
|
+
opacity: 0;
|
|
100
|
+
width: 0;
|
|
101
|
+
height: 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* The slider */
|
|
105
|
+
.slider {
|
|
106
|
+
position: absolute;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
top: 0;
|
|
109
|
+
left: 0;
|
|
110
|
+
right: 0;
|
|
111
|
+
bottom: 0;
|
|
112
|
+
background-color: var(--checkbox-disabled-bg);
|
|
113
|
+
-webkit-transition: .4s;
|
|
114
|
+
transition: .4s;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.slider:before {
|
|
118
|
+
position: absolute;
|
|
119
|
+
content: "";
|
|
120
|
+
height: $toggle-height;
|
|
121
|
+
width: $toggle-height;
|
|
122
|
+
left: 4px;
|
|
123
|
+
bottom: 4px;
|
|
124
|
+
background-color: var(--checkbox-tick);
|
|
125
|
+
-webkit-transition: .4s;
|
|
126
|
+
transition: .4s;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
input:checked + .slider {
|
|
130
|
+
background-color: var(--checkbox-ticked-bg);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
input:focus + .slider {
|
|
134
|
+
box-shadow: 0 0 1px var(--checkbox-ticked-bg);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
input:checked + .slider:before {
|
|
138
|
+
transform: translateX(24px);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Rounded sliders */
|
|
142
|
+
.slider.round {
|
|
143
|
+
border-radius: 34px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.slider.round:before {
|
|
147
|
+
border-radius: 50%;
|
|
148
|
+
}
|
|
149
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as ToggleSwitch } from './ToggleSwitch.vue';
|