@carbon/vue 3.0.4-alpha.0 → 3.0.6-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/carbon-vue-3.common.js +11782 -2709
- package/dist/carbon-vue-3.common.js.map +1 -1
- package/dist/carbon-vue-3.css +1 -0
- package/dist/carbon-vue-3.umd.js +11782 -2709
- package/dist/carbon-vue-3.umd.js.map +1 -1
- package/dist/carbon-vue-3.umd.min.js +30 -2
- package/dist/carbon-vue-3.umd.min.js.map +1 -1
- package/package.json +3 -2
- package/src/components/CvCheckbox/CvCheckbox.vue +0 -2
- package/src/components/CvDatePicker/CvDatePicker-notes.md +86 -0
- package/src/components/CvDatePicker/CvDatePicker.stories.js +241 -0
- package/src/components/CvDatePicker/CvDatePicker.vue +402 -0
- package/src/components/CvDatePicker/CvDatePickerSkeleton.vue +106 -0
- package/src/components/CvDatePicker/index.js +4 -0
- package/src/components/CvDatePicker/plugins/appendToPlugin.js +64 -0
- package/src/components/CvDatePicker/plugins/fixEventsPlugin.js +79 -0
- package/src/components/CvDatePicker/plugins/monthSelectPlugin.js +86 -0
- package/src/components/CvDatePicker/plugins/rangePlugin.js +47 -0
- package/src/components/CvForm/CvForm.stories.mdx +193 -0
- package/src/components/CvForm/CvForm.vue +9 -0
- package/src/components/CvForm/CvFormGroup.vue +27 -0
- package/src/components/CvForm/CvFormItem.vue +9 -0
- package/src/components/CvForm/index.js +5 -0
- package/src/components/CvModal/CvModal.stories.mdx +234 -0
- package/src/components/CvModal/CvModal.vue +403 -0
- package/src/components/CvModal/index.js +3 -0
- package/src/components/CvTextArea/CvTextArea.stories.js +174 -0
- package/src/components/CvTextArea/CvTextArea.vue +107 -0
- package/src/components/CvTextArea/index.js +4 -0
- package/src/components/CvTextInput/CvTextInput.stories.js +245 -0
- package/src/components/CvTextInput/CvTextInput.vue +217 -0
- package/src/components/CvTextInput/const.js +2 -0
- package/src/components/CvTextInput/index.js +4 -0
- package/src/components/CvTile/CvTile.stories.mdx +156 -0
- package/src/components/CvTile/CvTile.vue +67 -0
- package/src/components/CvTile/CvTileClickable.vue +23 -0
- package/src/components/CvTile/CvTileExpandable.vue +103 -0
- package/src/components/CvTile/CvTileSelectable.vue +52 -0
- package/src/components/CvTile/CvTileStandard.vue +7 -0
- package/src/components/CvTile/index.js +4 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Teleport to="body" :disabled="disableTeleport">
|
|
3
|
+
<div
|
|
4
|
+
ref="el"
|
|
5
|
+
data-modal
|
|
6
|
+
:id="uid"
|
|
7
|
+
:class="[
|
|
8
|
+
`cv-modal ${carbonPrefix}--modal`,
|
|
9
|
+
{
|
|
10
|
+
'is-visible': dataVisible,
|
|
11
|
+
[`${carbonPrefix}--modal--danger`]: kind === 'danger',
|
|
12
|
+
},
|
|
13
|
+
]"
|
|
14
|
+
tabindex="-1"
|
|
15
|
+
@keydown.esc.prevent="onEsc"
|
|
16
|
+
@click.self="onExternalClick"
|
|
17
|
+
v-bind="$attrs"
|
|
18
|
+
>
|
|
19
|
+
<div
|
|
20
|
+
:class="[
|
|
21
|
+
`${carbonPrefix}--modal-container`,
|
|
22
|
+
{
|
|
23
|
+
[`${carbonPrefix}--modal-container--${internalSize}`]: internalSize,
|
|
24
|
+
},
|
|
25
|
+
]"
|
|
26
|
+
v-bind="dialogAttrs"
|
|
27
|
+
:aria-label="ariaLabel"
|
|
28
|
+
ref="modalDialog"
|
|
29
|
+
>
|
|
30
|
+
<div
|
|
31
|
+
class="cv-modal__before-content"
|
|
32
|
+
ref="beforeContent"
|
|
33
|
+
tabindex="0"
|
|
34
|
+
style="position: absolute; height: 1px; width: 1px; left: -9999px"
|
|
35
|
+
role="separator"
|
|
36
|
+
aria-valuenow="0"
|
|
37
|
+
@focus="focusBeforeContent"
|
|
38
|
+
/>
|
|
39
|
+
<div :class="`${carbonPrefix}--modal-header`">
|
|
40
|
+
<p :class="`${carbonPrefix}--modal-header__label`">
|
|
41
|
+
<slot name="label"></slot>
|
|
42
|
+
</p>
|
|
43
|
+
<p :class="`${carbonPrefix}--modal-header__heading`">
|
|
44
|
+
<slot name="title">Modal Title</slot>
|
|
45
|
+
</p>
|
|
46
|
+
<button
|
|
47
|
+
:class="`${carbonPrefix}--modal-close`"
|
|
48
|
+
type="button"
|
|
49
|
+
@click="onClose"
|
|
50
|
+
ref="closeBtn"
|
|
51
|
+
:aria-label="closeAriaLabel"
|
|
52
|
+
>
|
|
53
|
+
<Close16 :class="`${carbonPrefix}--modal-close__icon`" />
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div
|
|
58
|
+
:class="[
|
|
59
|
+
`${carbonPrefix}--modal-content`,
|
|
60
|
+
{ [`${carbonPrefix}--modal-content--with-form`]: hasFormContent },
|
|
61
|
+
]"
|
|
62
|
+
ref="content"
|
|
63
|
+
:tabindex="data.scrollable ? 0 : undefined"
|
|
64
|
+
>
|
|
65
|
+
<slot name="content"></slot>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<cv-button-set
|
|
69
|
+
:class="[
|
|
70
|
+
`${carbonPrefix}--modal-footer`,
|
|
71
|
+
{
|
|
72
|
+
[`${carbonPrefix}--modal-footer--three-button`]:
|
|
73
|
+
data.hasPrimary && data.hasSecondary && data.hasOtherBtn,
|
|
74
|
+
},
|
|
75
|
+
]"
|
|
76
|
+
v-if="data.hasFooter"
|
|
77
|
+
>
|
|
78
|
+
<cv-button
|
|
79
|
+
type="button"
|
|
80
|
+
kind="secondary"
|
|
81
|
+
@click="onOtherBtnClick"
|
|
82
|
+
v-if="data.hasOtherBtn"
|
|
83
|
+
ref="otherBtn"
|
|
84
|
+
>
|
|
85
|
+
<slot name="other-button">Other button</slot>
|
|
86
|
+
</cv-button>
|
|
87
|
+
<cv-button
|
|
88
|
+
type="button"
|
|
89
|
+
kind="secondary"
|
|
90
|
+
@click="onSecondaryClick"
|
|
91
|
+
v-if="data.hasSecondary"
|
|
92
|
+
ref="secondaryBtn"
|
|
93
|
+
>
|
|
94
|
+
<slot name="secondary-button">Secondary button</slot>
|
|
95
|
+
</cv-button>
|
|
96
|
+
<cv-button
|
|
97
|
+
:disabled="primaryButtonDisabled"
|
|
98
|
+
type="button"
|
|
99
|
+
:kind="primaryKind"
|
|
100
|
+
@click="onPrimaryClick"
|
|
101
|
+
v-if="data.hasPrimary"
|
|
102
|
+
ref="primary"
|
|
103
|
+
>
|
|
104
|
+
<slot name="primary-button">Primary button</slot>
|
|
105
|
+
</cv-button>
|
|
106
|
+
</cv-button-set>
|
|
107
|
+
<div
|
|
108
|
+
class="cv-modal__after-content"
|
|
109
|
+
ref="afterContent"
|
|
110
|
+
tabindex="0"
|
|
111
|
+
style="position: absolute; height: 1px; width: 1px; left: -9999px"
|
|
112
|
+
role="separator"
|
|
113
|
+
aria-valuenow="1"
|
|
114
|
+
@focus="focusAfterContent"
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</Teleport>
|
|
119
|
+
</template>
|
|
120
|
+
|
|
121
|
+
<script setup>
|
|
122
|
+
import CvButton from '../CvButton/CvButton.vue';
|
|
123
|
+
import { carbonPrefix } from '../../global/settings';
|
|
124
|
+
import { props as propsCvId, useCvId } from '../../use/cvId';
|
|
125
|
+
import Close16 from '@carbon/icons-vue/es/close/16';
|
|
126
|
+
import CvButtonSet from '../CvButton/CvButtonSet.vue';
|
|
127
|
+
import {
|
|
128
|
+
computed,
|
|
129
|
+
onBeforeMount,
|
|
130
|
+
onBeforeUnmount,
|
|
131
|
+
onBeforeUpdate,
|
|
132
|
+
onMounted,
|
|
133
|
+
reactive,
|
|
134
|
+
ref,
|
|
135
|
+
useSlots,
|
|
136
|
+
watch,
|
|
137
|
+
} from 'vue';
|
|
138
|
+
|
|
139
|
+
const props = defineProps({
|
|
140
|
+
/**
|
|
141
|
+
* When set to true, the aria role will be either "alert" or "alertdialog"
|
|
142
|
+
*/
|
|
143
|
+
alert: { type: Boolean, default: false },
|
|
144
|
+
/**
|
|
145
|
+
* optional label for close button, default 'Close modal'
|
|
146
|
+
*/
|
|
147
|
+
closeAriaLabel: { type: String, default: 'Close modal' },
|
|
148
|
+
/**
|
|
149
|
+
* aria label for the dialog, default 'Information modal'
|
|
150
|
+
*/
|
|
151
|
+
ariaLabel: { type: String, default: 'Information modal' },
|
|
152
|
+
/**
|
|
153
|
+
* 'danger' otherwise default modal.
|
|
154
|
+
*/
|
|
155
|
+
kind: {
|
|
156
|
+
type: String,
|
|
157
|
+
default: '',
|
|
158
|
+
validator: val => ['', 'danger'].includes(val),
|
|
159
|
+
},
|
|
160
|
+
/**
|
|
161
|
+
* boolean value if true the component user is expected to close the modal via visible property.
|
|
162
|
+
*/
|
|
163
|
+
autoHideOff: { type: Boolean, default: false },
|
|
164
|
+
/**
|
|
165
|
+
* visibility of modal dialog - available as v-model
|
|
166
|
+
*/
|
|
167
|
+
visible: { type: Boolean, default: false },
|
|
168
|
+
/**
|
|
169
|
+
* @deprecated - use v-model:visible
|
|
170
|
+
*/
|
|
171
|
+
modelValue: {
|
|
172
|
+
type: Boolean,
|
|
173
|
+
validator: val => {
|
|
174
|
+
console.warn(
|
|
175
|
+
`v-model for modal is deprecated. Specify "v-model:visible" instead [${val}]`
|
|
176
|
+
);
|
|
177
|
+
return true;
|
|
178
|
+
},
|
|
179
|
+
default: undefined,
|
|
180
|
+
},
|
|
181
|
+
/**
|
|
182
|
+
* boolean value used to enable/disable the primary button
|
|
183
|
+
*/
|
|
184
|
+
primaryButtonDisabled: { type: Boolean, default: false },
|
|
185
|
+
/**
|
|
186
|
+
* default size is "medium"
|
|
187
|
+
*/
|
|
188
|
+
size: {
|
|
189
|
+
type: String,
|
|
190
|
+
validator: val =>
|
|
191
|
+
['', 'xs', 'sm', 'small', 'md', 'large', 'lg'].includes(val),
|
|
192
|
+
default: '',
|
|
193
|
+
},
|
|
194
|
+
/**
|
|
195
|
+
* Adds styling specific to modals with form content
|
|
196
|
+
*/
|
|
197
|
+
hasFormContent: { type: Boolean, default: false },
|
|
198
|
+
/**
|
|
199
|
+
* Do not move the modal to the document body
|
|
200
|
+
*/
|
|
201
|
+
disableTeleport: { type: Boolean, default: false },
|
|
202
|
+
|
|
203
|
+
// Listeners
|
|
204
|
+
onPrimaryClick: { type: Function },
|
|
205
|
+
onSecondaryClick: { type: Function },
|
|
206
|
+
onOtherBtnClick: { type: Function },
|
|
207
|
+
|
|
208
|
+
...propsCvId,
|
|
209
|
+
});
|
|
210
|
+
const uid = useCvId(props, true);
|
|
211
|
+
|
|
212
|
+
const dataVisible = ref(false);
|
|
213
|
+
const data = reactive({
|
|
214
|
+
scrollable: false,
|
|
215
|
+
hasFooter: false,
|
|
216
|
+
hasHeaderLabel: false,
|
|
217
|
+
hasPrimary: false,
|
|
218
|
+
hasSecondary: false,
|
|
219
|
+
hasOtherBtn: false,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const slots = useSlots();
|
|
223
|
+
function checkSlots() {
|
|
224
|
+
// NOTE: $slots is not reactive so needs to be managed on updated
|
|
225
|
+
data.hasFooter = !!(
|
|
226
|
+
slots['primary-button'] ||
|
|
227
|
+
slots['secondary-button'] ||
|
|
228
|
+
slots['other-button']
|
|
229
|
+
);
|
|
230
|
+
data.hasHeaderLabel = !!slots['label'];
|
|
231
|
+
data.hasPrimary = !!slots['primary-button'];
|
|
232
|
+
data.hasSecondary = !!slots['secondary-button'];
|
|
233
|
+
data.hasOtherBtn = !!slots['other-button'];
|
|
234
|
+
}
|
|
235
|
+
const emit = defineEmits([
|
|
236
|
+
'modal-shown',
|
|
237
|
+
'modal-hidden',
|
|
238
|
+
'after-modal-hidden',
|
|
239
|
+
'modal-hide-request',
|
|
240
|
+
'primary-click',
|
|
241
|
+
'secondary-click',
|
|
242
|
+
'other-btn-click',
|
|
243
|
+
'update:modelValue',
|
|
244
|
+
'update:visible',
|
|
245
|
+
]);
|
|
246
|
+
onBeforeMount(checkSlots);
|
|
247
|
+
onBeforeUpdate(checkSlots);
|
|
248
|
+
|
|
249
|
+
const el = ref(null);
|
|
250
|
+
function show() {
|
|
251
|
+
// prevent body scrolling
|
|
252
|
+
document.body.classList.add(`${carbonPrefix}--body--with-modal-open`);
|
|
253
|
+
|
|
254
|
+
el.value?.addEventListener('transitionend', onShown);
|
|
255
|
+
dataVisible.value = true;
|
|
256
|
+
}
|
|
257
|
+
const content = ref(null);
|
|
258
|
+
function onShown() {
|
|
259
|
+
const focusEl = content.value?.querySelector('[data-modal-primary-focus]');
|
|
260
|
+
if (focusEl) {
|
|
261
|
+
focusEl.focus();
|
|
262
|
+
} else {
|
|
263
|
+
focusBeforeContent();
|
|
264
|
+
}
|
|
265
|
+
emit('modal-shown');
|
|
266
|
+
|
|
267
|
+
// check to see if content scrollable
|
|
268
|
+
data.scrollable = content.value?.scrollHeight > content.value?.clientHeight;
|
|
269
|
+
|
|
270
|
+
el.value?.removeEventListener('transitionend', onShown);
|
|
271
|
+
}
|
|
272
|
+
const secondaryBtn = ref(null);
|
|
273
|
+
const otherBtn = ref(null);
|
|
274
|
+
const closeBtn = ref(null);
|
|
275
|
+
function focusBeforeContent() {
|
|
276
|
+
if (slots['primary-button']) {
|
|
277
|
+
el.value?.focus();
|
|
278
|
+
} else if (slots['secondary-button']) {
|
|
279
|
+
secondaryBtn.value?.$el.focus();
|
|
280
|
+
} else if (slots['other-button']) {
|
|
281
|
+
otherBtn.value?.$el.focus();
|
|
282
|
+
} else {
|
|
283
|
+
closeBtn.value?.focus();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
onMounted(() => {
|
|
288
|
+
if (props.visible) show();
|
|
289
|
+
else if (props.modelValue) show();
|
|
290
|
+
});
|
|
291
|
+
watch(
|
|
292
|
+
() => props.visible,
|
|
293
|
+
val => {
|
|
294
|
+
if (val) show();
|
|
295
|
+
else hide();
|
|
296
|
+
}
|
|
297
|
+
);
|
|
298
|
+
watch(dataVisible, val => {
|
|
299
|
+
emit('update:modelValue', val);
|
|
300
|
+
emit('update:visible', val);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
function hide() {
|
|
304
|
+
//restore any previous scroll
|
|
305
|
+
document.body.classList.remove(`${carbonPrefix}--body--with-modal-open`);
|
|
306
|
+
|
|
307
|
+
if (dataVisible.value) {
|
|
308
|
+
el.value?.addEventListener('transitionend', afterHide);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
dataVisible.value = false;
|
|
312
|
+
emit('modal-hidden');
|
|
313
|
+
}
|
|
314
|
+
function afterHide(event) {
|
|
315
|
+
if (event.propertyName === 'opacity') {
|
|
316
|
+
emit('after-modal-hidden');
|
|
317
|
+
el.value?.removeEventListener('transitionend', afterHide);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const dialogAttrs = computed(() => {
|
|
322
|
+
const passive = !data.hasFooter;
|
|
323
|
+
const attrs = {};
|
|
324
|
+
if (props.alert) {
|
|
325
|
+
if (passive) {
|
|
326
|
+
attrs.role = 'alert';
|
|
327
|
+
} else {
|
|
328
|
+
attrs.role = 'alertdialog';
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
attrs.role = 'dialog';
|
|
332
|
+
}
|
|
333
|
+
return attrs;
|
|
334
|
+
});
|
|
335
|
+
const primaryKind = computed(() => {
|
|
336
|
+
if (props.kind === 'danger') {
|
|
337
|
+
return 'danger';
|
|
338
|
+
} else {
|
|
339
|
+
return 'primary';
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
const internalSize = computed(() => {
|
|
343
|
+
switch (props.size) {
|
|
344
|
+
case 'xs':
|
|
345
|
+
return 'xs';
|
|
346
|
+
case 'sm':
|
|
347
|
+
case 'small':
|
|
348
|
+
return 'sm';
|
|
349
|
+
case 'lg':
|
|
350
|
+
case 'large':
|
|
351
|
+
return 'lg';
|
|
352
|
+
default:
|
|
353
|
+
return '';
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
function focusAfterContent() {
|
|
358
|
+
closeBtn.value?.focus();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function onExternalClick(ev) {
|
|
362
|
+
if (ev.target === el.value) {
|
|
363
|
+
_maybeHide(ev, 'external-click');
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function onEsc(ev) {
|
|
367
|
+
_maybeHide(ev, 'escape-press');
|
|
368
|
+
}
|
|
369
|
+
function onClose(ev) {
|
|
370
|
+
_maybeHide(ev, 'close-click');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function _maybeHide(event, reason) {
|
|
374
|
+
if (!props.autoHideOff) {
|
|
375
|
+
hide();
|
|
376
|
+
} else {
|
|
377
|
+
emit('modal-hide-request', { 'cv:reason': reason });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function onFooterButtonClick(buttonId, ev) {
|
|
381
|
+
emit(buttonId);
|
|
382
|
+
if (buttonId === 'primary-click' && !props.onPrimaryClick)
|
|
383
|
+
_maybeHide(ev, buttonId);
|
|
384
|
+
if (buttonId === 'secondary-click' && !props.onSecondaryClick)
|
|
385
|
+
_maybeHide(ev, buttonId);
|
|
386
|
+
if (buttonId === 'other-btn-click' && !props.onOtherBtnClick)
|
|
387
|
+
_maybeHide(ev, buttonId);
|
|
388
|
+
}
|
|
389
|
+
function onPrimaryClick(ev) {
|
|
390
|
+
onFooterButtonClick('primary-click', ev);
|
|
391
|
+
}
|
|
392
|
+
function onSecondaryClick(ev) {
|
|
393
|
+
onFooterButtonClick('secondary-click', ev);
|
|
394
|
+
}
|
|
395
|
+
function onOtherBtnClick(ev) {
|
|
396
|
+
onFooterButtonClick('other-btn-click', ev);
|
|
397
|
+
}
|
|
398
|
+
onBeforeUnmount(() => {
|
|
399
|
+
if (dataVisible.value) {
|
|
400
|
+
emit('after-modal-hidden');
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
</script>
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sbCompPrefix,
|
|
3
|
+
storyParametersObject,
|
|
4
|
+
} from '../../global/storybook-utils';
|
|
5
|
+
|
|
6
|
+
import CvTextArea from '.';
|
|
7
|
+
import { ref } from 'vue';
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
title: `${sbCompPrefix}/CvTextArea`,
|
|
11
|
+
component: CvTextArea,
|
|
12
|
+
argTypes: {
|
|
13
|
+
// attrs
|
|
14
|
+
disabled: {
|
|
15
|
+
type: 'boolean',
|
|
16
|
+
table: {
|
|
17
|
+
type: { summary: 'boolean' },
|
|
18
|
+
category: 'attributes',
|
|
19
|
+
},
|
|
20
|
+
description: 'Specify whether the textarea should be disabled',
|
|
21
|
+
},
|
|
22
|
+
placeholder: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
table: {
|
|
25
|
+
type: { summary: 'string' },
|
|
26
|
+
category: 'attributes',
|
|
27
|
+
},
|
|
28
|
+
description: 'Specify the placeholder attribute for the textarea',
|
|
29
|
+
},
|
|
30
|
+
// props
|
|
31
|
+
helperText: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
table: {
|
|
34
|
+
type: { summary: 'string' },
|
|
35
|
+
category: 'props',
|
|
36
|
+
},
|
|
37
|
+
description:
|
|
38
|
+
'Provide text that is used alongside the control label for additional help',
|
|
39
|
+
},
|
|
40
|
+
hideLabel: {
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
table: {
|
|
43
|
+
type: { summary: 'boolean' },
|
|
44
|
+
category: 'props',
|
|
45
|
+
defaultValue: false,
|
|
46
|
+
},
|
|
47
|
+
description:
|
|
48
|
+
'Specify whether you want the underlying label to be visually hidden',
|
|
49
|
+
},
|
|
50
|
+
invalidMessage: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
table: {
|
|
53
|
+
type: { summary: 'string' },
|
|
54
|
+
category: 'props',
|
|
55
|
+
},
|
|
56
|
+
description:
|
|
57
|
+
'Provide the text that is displayed when the control is in an invalid state',
|
|
58
|
+
},
|
|
59
|
+
label: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
table: {
|
|
62
|
+
type: { summary: 'string' },
|
|
63
|
+
category: 'props',
|
|
64
|
+
},
|
|
65
|
+
description: "Input's label",
|
|
66
|
+
},
|
|
67
|
+
light: {
|
|
68
|
+
type: 'boolean',
|
|
69
|
+
table: {
|
|
70
|
+
type: { summary: 'boolean' },
|
|
71
|
+
category: 'props',
|
|
72
|
+
defaultValue: false,
|
|
73
|
+
},
|
|
74
|
+
description:
|
|
75
|
+
"Toggles light version. For use on `$ui-01` backgrounds only. Don't use this to make tile background color same as container background color.",
|
|
76
|
+
},
|
|
77
|
+
modelValue: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
table: {
|
|
80
|
+
type: { summary: 'string' },
|
|
81
|
+
category: 'props',
|
|
82
|
+
},
|
|
83
|
+
description:
|
|
84
|
+
"Input's value, modelValue is the vue3 default 'prop' for two-way data binding with v-model",
|
|
85
|
+
},
|
|
86
|
+
// slots
|
|
87
|
+
'helper-text': {
|
|
88
|
+
type: 'string',
|
|
89
|
+
table: {
|
|
90
|
+
type: { summary: 'string | html | Component' },
|
|
91
|
+
category: 'slots',
|
|
92
|
+
},
|
|
93
|
+
description:
|
|
94
|
+
"Helper text slot. It isn't shown if invalid message, either prop or slot, is available",
|
|
95
|
+
},
|
|
96
|
+
'invalid-message': {
|
|
97
|
+
type: 'string',
|
|
98
|
+
table: {
|
|
99
|
+
type: { summary: 'string | html | Component' },
|
|
100
|
+
category: 'slots',
|
|
101
|
+
},
|
|
102
|
+
description: 'Invalid message slot.',
|
|
103
|
+
},
|
|
104
|
+
// events
|
|
105
|
+
'update:modelValue': {
|
|
106
|
+
type: 'event',
|
|
107
|
+
table: {
|
|
108
|
+
category: 'events',
|
|
109
|
+
},
|
|
110
|
+
control: { type: null },
|
|
111
|
+
description:
|
|
112
|
+
"Triggered on textarea update. `update:modelValue` is the vue3 default 'event' for two-way data binding with v-model",
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const template = `
|
|
118
|
+
<cv-text-area v-bind="args">
|
|
119
|
+
<template v-if="args['helper-text']" v-slot:helper-text />
|
|
120
|
+
<template v-if="args['invalid-message']" v-slot:invalid-message />
|
|
121
|
+
</cv-text-area>
|
|
122
|
+
`;
|
|
123
|
+
const Template = args => {
|
|
124
|
+
return {
|
|
125
|
+
components: { CvTextArea },
|
|
126
|
+
setup: () => ({ args }),
|
|
127
|
+
template,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const Default = Template.bind({});
|
|
132
|
+
Default.args = {
|
|
133
|
+
label: 'Text area label',
|
|
134
|
+
placeholder: 'Sample placeholder',
|
|
135
|
+
};
|
|
136
|
+
Default.parameters = storyParametersObject(
|
|
137
|
+
Default.parameters,
|
|
138
|
+
template,
|
|
139
|
+
Default.args
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
export const Slots = Template.bind({});
|
|
143
|
+
Slots.args = {
|
|
144
|
+
['invalid-message']: 'Invalid text area message',
|
|
145
|
+
label: 'Text area label',
|
|
146
|
+
placeholder: 'Sample placeholder',
|
|
147
|
+
};
|
|
148
|
+
Slots.parameters = storyParametersObject(
|
|
149
|
+
Slots.parameters,
|
|
150
|
+
template,
|
|
151
|
+
Slots.args
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const vModelTemplate = `
|
|
155
|
+
<cv-text-area v-bind="args" v-model="value" />
|
|
156
|
+
<p style="margin-top: 1rem;">Value passed to v-model: {{ value }}</p>
|
|
157
|
+
`;
|
|
158
|
+
const VModelTemplate = args => {
|
|
159
|
+
return {
|
|
160
|
+
components: { CvTextArea },
|
|
161
|
+
setup: () => ({ args, value: ref('') }),
|
|
162
|
+
template: vModelTemplate,
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
export const vModel = VModelTemplate.bind({});
|
|
166
|
+
vModel.args = {
|
|
167
|
+
label: 'Text area label',
|
|
168
|
+
placeholder: 'Sample placeholder',
|
|
169
|
+
};
|
|
170
|
+
vModel.parameters = storyParametersObject(
|
|
171
|
+
vModel.parameters,
|
|
172
|
+
vModelTemplate,
|
|
173
|
+
vModel.args
|
|
174
|
+
);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="`cv-text-area ${carbonPrefix}--form-item`">
|
|
3
|
+
<label
|
|
4
|
+
:for="cvId"
|
|
5
|
+
:class="[
|
|
6
|
+
`${carbonPrefix}--label`,
|
|
7
|
+
{
|
|
8
|
+
[`${carbonPrefix}--label--disabled`]: $attrs.disabled,
|
|
9
|
+
[`${carbonPrefix}--visually-hidden`]: hideLabel,
|
|
10
|
+
},
|
|
11
|
+
]"
|
|
12
|
+
>
|
|
13
|
+
{{ label }}
|
|
14
|
+
</label>
|
|
15
|
+
<div
|
|
16
|
+
:class="`${carbonPrefix}--text-area__wrapper`"
|
|
17
|
+
:data-invalid="isInvalid"
|
|
18
|
+
>
|
|
19
|
+
<WarningFilled16
|
|
20
|
+
v-if="isInvalid"
|
|
21
|
+
:class="`${carbonPrefix}--text-area__invalid-icon`"
|
|
22
|
+
/>
|
|
23
|
+
<textarea
|
|
24
|
+
:aria-invalid="isInvalid"
|
|
25
|
+
:aria-describedby="isInvalid ? errorId : undefined"
|
|
26
|
+
:id="cvId"
|
|
27
|
+
:class="[
|
|
28
|
+
`${carbonPrefix}--text-area`,
|
|
29
|
+
{
|
|
30
|
+
[`${carbonPrefix}--text-area--invalid`]: isInvalid,
|
|
31
|
+
[`${carbonPrefix}--text-area--light`]: isLight,
|
|
32
|
+
},
|
|
33
|
+
]"
|
|
34
|
+
v-bind="$attrs"
|
|
35
|
+
:value="modelValue"
|
|
36
|
+
@input="$event => $emit('update:modelValue', $event.target.value)"
|
|
37
|
+
></textarea>
|
|
38
|
+
</div>
|
|
39
|
+
<div
|
|
40
|
+
v-if="isInvalid"
|
|
41
|
+
:id="errorId"
|
|
42
|
+
:class="`${carbonPrefix}--form-requirement`"
|
|
43
|
+
>
|
|
44
|
+
<slot name="invalid-message">{{ invalidMessage }}</slot>
|
|
45
|
+
</div>
|
|
46
|
+
<div
|
|
47
|
+
v-if="isHelper"
|
|
48
|
+
:class="[
|
|
49
|
+
`${carbonPrefix}--form__helper-text`,
|
|
50
|
+
{ [`${carbonPrefix}--form__helper-text--disabled`]: $attrs.disabled },
|
|
51
|
+
]"
|
|
52
|
+
>
|
|
53
|
+
<slot name="helper-text">{{ helperText }}</slot>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<script setup>
|
|
59
|
+
import { computed, ref, useSlots, onBeforeMount, onBeforeUpdate } from 'vue';
|
|
60
|
+
import { WarningFilled16 } from '@carbon/icons-vue';
|
|
61
|
+
import { carbonPrefix } from '../../global/settings';
|
|
62
|
+
import { useCvId, props as propsCvId } from '../../use/cvId';
|
|
63
|
+
import { useIsLight, props as propsTheme } from '../../use/cvTheme';
|
|
64
|
+
|
|
65
|
+
const props = defineProps({
|
|
66
|
+
helperText: { type: String, default: undefined },
|
|
67
|
+
hideLabel: { type: Boolean, default: false },
|
|
68
|
+
invalidMessage: { type: String, default: undefined },
|
|
69
|
+
label: String,
|
|
70
|
+
modelValue: String,
|
|
71
|
+
...propsCvId,
|
|
72
|
+
...propsTheme,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const cvId = useCvId(props);
|
|
76
|
+
|
|
77
|
+
// DOM
|
|
78
|
+
const slots = useSlots();
|
|
79
|
+
|
|
80
|
+
// Data
|
|
81
|
+
const isHelper = ref(false);
|
|
82
|
+
const isInvalid = ref(false);
|
|
83
|
+
const isLight = useIsLight(props);
|
|
84
|
+
|
|
85
|
+
// Computed
|
|
86
|
+
const errorId = computed(() => `error-${cvId.value}`);
|
|
87
|
+
|
|
88
|
+
// Methods
|
|
89
|
+
function checkSlots() {
|
|
90
|
+
// NOTE: this.$slots is not reactive so needs to be managed on updated
|
|
91
|
+
isInvalid.value = !!(
|
|
92
|
+
props.invalidMessage?.length || slots['invalid-message']
|
|
93
|
+
);
|
|
94
|
+
isHelper.value =
|
|
95
|
+
!isInvalid.value && !!(props.helperText?.length || slots['helper-text']);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Life Hooks
|
|
99
|
+
onBeforeMount(checkSlots);
|
|
100
|
+
onBeforeUpdate(checkSlots);
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<script>
|
|
104
|
+
export default {
|
|
105
|
+
inheritAttrs: false,
|
|
106
|
+
};
|
|
107
|
+
</script>
|