@carbon/vue 3.0.5-alpha.0 → 3.0.7-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.
Files changed (64) hide show
  1. package/README.md +16 -0
  2. package/dist/carbon-vue-3.common.js +22859 -11008
  3. package/dist/carbon-vue-3.common.js.map +1 -1
  4. package/dist/carbon-vue-3.css +1 -0
  5. package/dist/carbon-vue-3.umd.js +22859 -11008
  6. package/dist/carbon-vue-3.umd.js.map +1 -1
  7. package/dist/carbon-vue-3.umd.min.js +30 -2
  8. package/dist/carbon-vue-3.umd.min.js.map +1 -1
  9. package/package.json +5 -4
  10. package/src/components/CvAccordion/CvAccordion.stories.js +1 -0
  11. package/src/components/CvButton/CvButton.stories.js +3 -3
  12. package/src/components/CvButton/CvButtonSkeleton.stories.js +79 -0
  13. package/src/components/CvButton/CvButtonSkeleton.vue +19 -0
  14. package/src/components/CvButton/index.js +8 -1
  15. package/src/components/CvDatePicker/CvDatePicker-notes.md +86 -0
  16. package/src/components/CvDatePicker/CvDatePicker.stories.js +241 -0
  17. package/src/components/CvDatePicker/CvDatePicker.vue +402 -0
  18. package/src/components/CvDatePicker/CvDatePickerSkeleton.vue +106 -0
  19. package/src/components/CvDatePicker/index.js +4 -0
  20. package/src/components/CvDatePicker/plugins/appendToPlugin.js +64 -0
  21. package/src/components/CvDatePicker/plugins/fixEventsPlugin.js +79 -0
  22. package/src/components/CvDatePicker/plugins/monthSelectPlugin.js +86 -0
  23. package/src/components/CvDatePicker/plugins/rangePlugin.js +47 -0
  24. package/src/components/CvDropdown/CvDropdown.stories.mdx +407 -0
  25. package/src/components/CvDropdown/CvDropdown.vue +418 -0
  26. package/src/components/CvDropdown/CvDropdownItem.vue +86 -0
  27. package/src/components/CvDropdown/CvDropdownSkeleton.vue +17 -0
  28. package/src/components/CvDropdown/index.js +5 -0
  29. package/src/components/CvForm/CvForm.stories.mdx +193 -0
  30. package/src/components/CvForm/CvForm.vue +9 -0
  31. package/src/components/CvForm/CvFormGroup.vue +27 -0
  32. package/src/components/CvForm/CvFormItem.vue +9 -0
  33. package/src/components/CvForm/index.js +5 -0
  34. package/src/components/CvInlineLoading/CvInlineLoading.stories.mdx +87 -0
  35. package/src/components/CvInlineLoading/CvInlineLoading.vue +144 -0
  36. package/src/components/CvInlineLoading/consts.js +9 -0
  37. package/src/components/CvInlineLoading/index.js +4 -0
  38. package/src/components/CvModal/CvModal.stories.mdx +236 -0
  39. package/src/components/CvModal/CvModal.vue +401 -0
  40. package/src/components/CvModal/index.js +3 -0
  41. package/src/components/CvMultiSelect/CvMultiSelect.stories.mdx +333 -0
  42. package/src/components/CvMultiSelect/CvMultiSelect.vue +705 -0
  43. package/src/components/CvMultiSelect/consts.js +16 -0
  44. package/src/components/CvMultiSelect/index.js +3 -0
  45. package/src/components/CvNumberInput/CvNumberInput.stories.js +265 -0
  46. package/src/components/CvNumberInput/CvNumberInput.vue +227 -0
  47. package/src/components/CvNumberInput/CvNumberInputSkeleton.stories.js +30 -0
  48. package/src/components/CvNumberInput/CvNumberInputSkeleton.vue +10 -0
  49. package/src/components/CvNumberInput/StorybookGroupConst.js +1 -0
  50. package/src/components/CvNumberInput/__tests__/__snapshots__/CvNumberInput.spec.js.snap +21 -0
  51. package/src/components/CvNumberInput/index.js +5 -0
  52. package/src/components/CvTabs/CvTab.vue +77 -0
  53. package/src/components/CvTabs/CvTabs.stories.mdx +175 -0
  54. package/src/components/CvTabs/CvTabs.vue +406 -0
  55. package/src/components/CvTabs/CvTabsSkeleton.vue +24 -0
  56. package/src/components/CvTabs/_SbContainer.vue +22 -0
  57. package/src/components/CvTabs/index.js +5 -0
  58. package/src/components/CvToggle/CvToggle-Skeleton.vue +29 -0
  59. package/src/components/CvToggle/CvToggle.stories.js +232 -0
  60. package/src/components/CvToggle/CvToggle.vue +70 -0
  61. package/src/components/CvToggle/__tests__/__snapshots__/CvToggle.spec.js.snap +7 -0
  62. package/src/components/CvToggle/index.js +4 -0
  63. package/src/global/storybook-utils/story-source-code.js +1 -0
  64. package/src/use/cvInput.js +34 -0
@@ -0,0 +1,175 @@
1
+ import { Canvas, Meta, Story } from '@storybook/addon-docs';
2
+ import { sbCompPrefix } from '../../global/storybook-utils';
3
+ import CvTabs from './CvTabs.vue';
4
+ import CvTab from './CvTab.vue';
5
+ import CvTabsSkeleton from './CvTabsSkeleton.vue';
6
+ import SbContainer from './_SbContainer.vue';
7
+ import { action } from '@storybook/addon-actions';
8
+
9
+ <Meta
10
+ title={`${sbCompPrefix}/CvTabs`}
11
+ component={CvTabs}
12
+ decorators={[
13
+ () => ({
14
+ components: { SbContainer, Story },
15
+ template: `<SbContainer><story/></SbContainer>`,
16
+ }),
17
+ ]}
18
+ />
19
+
20
+ export const Template = args => ({
21
+ // Components used in your story `template` are defined in the `components` object
22
+ components: {
23
+ CvTabs,
24
+ CvTab,
25
+ CvTabsSkeleton,
26
+ },
27
+ // The story's `args` need to be mapped into the template through the `setup()` method
28
+ setup() {
29
+ return {
30
+ container: args.container,
31
+ noDefaultToFirst: args.noDefaultToFirst,
32
+ selectedId: args.selectedId,
33
+ disabledIds: args.disabledIds,
34
+ onTabSelected: action('tab-selected'),
35
+ onTabSelectedId: action('tab-selected-id'),
36
+ };
37
+ },
38
+ // And then the `args` are bound to your component with `v-bind="args"`
39
+ template: args.template,
40
+ });
41
+ const defaultTemplate = `
42
+ <cv-tabs
43
+ :container="container"
44
+ :no-default-to-first="noDefaultToFirst"
45
+ @tab-selected="onTabSelected"
46
+ @tab-selected-id="onTabSelectedId"
47
+ aria-label="navigation tab label">
48
+ <cv-tab id="tab-1" label="House" :selected="selectedId === 'tab-1'" :disabled="disabledIds.includes('tab-1')">
49
+ <p>
50
+ 29 Arlington Avenue is a house in Islington, London. Douglas Adams shared the house for a time
51
+ with Jonny Brock and Clare Gorst. It is mentioned in the dedication of The Hitchhiker's Guide to
52
+ the Galaxy, which is dedicated to the "Arlingtonians for tea, sympathy, and a sofa."
53
+ </p>
54
+ </cv-tab>
55
+ <cv-tab id="tab-2" label="Bar" :selected="selectedId === 'tab-2'" :disabled="disabledIds.includes('tab-2')">
56
+ <p>
57
+ The Old Pink Dog Bar was the bar in Han Dold City that Ford Prefect was in when he discovered
58
+ that his Guide had his full entry on Earth on it.
59
+ </p>
60
+ </cv-tab>
61
+ <cv-tab id="tab-3" label="Reach" :selected="selectedId === 'tab-3'" :disabled="disabledIds.includes('tab-3')">
62
+ <p>
63
+ The Third Reach of the Unknown is what might be loosely described as a "place" which exists only
64
+ under high improbability conditions and was created on the starship Heart of Gold due to the
65
+ Infinite Improbability Drive. During Arthur Dent and Ford Prefect's first trip on board the Heart
66
+ of Gold they witnessed many strange things happening.
67
+ </p>
68
+ </cv-tab>
69
+ <cv-tab id="tab-4" label="Zone" :selected="selectedId === 'tab-4'" :disabled="disabledIds.includes('tab-4')">
70
+ <p>Plural Zones are instabilities in space-time.
71
+ The effects of the Plural Zones on the beings and objects that inhabit and originate from them,
72
+ for the most part, remain largely unnoticed.
73
+ </p>
74
+ </cv-tab>
75
+ <cv-tab id="tab-5" label="Planet" :selected="selectedId === 'tab-5'" :disabled="disabledIds.includes('tab-5')">
76
+ <p>
77
+ Ursa Minor Beta is a rich and sunny planet and one of the most popular holiday destinations in
78
+ the Universe. It is always Saturday afternoon on this planet, just before the beach bars close -
79
+ except for the few places where it is eternally Saturday evening. This is why the locals always
80
+ invite visitors to "Have a nice diurnal anomaly!"
81
+ <a target='_blank' href='https://hitchhikers.fandom.com/wiki/Main_Page'>credits</a>
82
+ </p>
83
+ </cv-tab>
84
+ </cv-tabs>
85
+ `;
86
+ const skeletonTemplate = `<cv-tabs-skeleton></cv-tabs-skeleton>`;
87
+
88
+ # CvTabs
89
+
90
+ Migration notes:
91
+
92
+ - The property `leftOverflowIconButtonProps` is deprecated. It does not seem to be used at all in Vue 2 and thus does
93
+ nothing when specified.
94
+ - The property `rightOverflowIconButtonProps` is deprecated. It does not seem to be used at all in Vue 2 and thus does
95
+ nothing when specified.
96
+ - The property `scrollIntoView` is deprecated. It does not seem to be used at all in Vue 2 and thus does nothing when
97
+ specified.
98
+ - The `container` property in Vue 2 acts differently as compared to the React story book. Here the class/styles are
99
+ applied as is shown in the React storybook. Also note that the background color applied when this property is
100
+ specified is not from Carbon. The selector `.bx--tabs--container ~ div` can be used to set the background color and
101
+ other properties but that is up to the user.
102
+ - A new event emits `tab-selected-id` with the selected tab's id attribute.
103
+
104
+ ## Mobile
105
+
106
+ In my opinion, this component is not suitable for mobile formats. The navigation of the tabs becomes awkward on smaller
107
+ screens, and so I suggest using a different component for mobile cases.
108
+
109
+ <Canvas>
110
+ <Story
111
+ name="Default"
112
+ parameters={{
113
+ controls: {
114
+ exclude: [
115
+ 'default',
116
+ 'template',
117
+ 'leftOverflowIconButtonProps',
118
+ 'rightOverflowIconButtonProps',
119
+ 'scrollIntoView',
120
+ 'tab-selected',
121
+ 'tab-selected-id',
122
+ ],
123
+ },
124
+ docs: { source: { code: defaultTemplate } },
125
+ }}
126
+ args={{
127
+ template: defaultTemplate,
128
+ selectedId: '',
129
+ disabledIds: [],
130
+ }}
131
+ argTypes={{
132
+ selectedId: {
133
+ control: 'select',
134
+ default: '',
135
+ options: ['', 'tab-1', 'tab-2', 'tab-3', 'tab-4', 'tab-5'],
136
+ },
137
+ disabledIds: {
138
+ control: 'multi-select',
139
+ options: ['tab-1', 'tab-2', 'tab-3', 'tab-4', 'tab-5'],
140
+ },
141
+ }}
142
+ >
143
+ {Template.bind({})}
144
+ </Story>
145
+ </Canvas>
146
+
147
+ # Skeleton
148
+
149
+ <Canvas>
150
+ <Story
151
+ name="skeleton"
152
+ parameters={{
153
+ controls: {
154
+ exclude: [
155
+ 'default',
156
+ 'template',
157
+ 'leftOverflowIconButtonProps',
158
+ 'rightOverflowIconButtonProps',
159
+ 'scrollIntoView',
160
+ 'tab-selected',
161
+ 'tab-selected-id',
162
+ 'container',
163
+ 'noDefaultToFirst',
164
+ ],
165
+ },
166
+ docs: { source: { code: skeletonTemplate } },
167
+ }}
168
+ args={{
169
+ template: skeletonTemplate,
170
+ }}
171
+ argTypes={{}}
172
+ >
173
+ {Template.bind({})}
174
+ </Story>
175
+ </Canvas>
@@ -0,0 +1,406 @@
1
+ <!--suppress ES6PreferShortImport -->
2
+ <template>
3
+ <div class="cv-tabs" style="width: 100%">
4
+ <div
5
+ data-tabs
6
+ :class="[
7
+ `cv-tab ${carbonPrefix}--tabs--scrollable`,
8
+ {
9
+ [`${carbonPrefix}--tabs--container`]: container,
10
+ [`${carbonPrefix}--tabs--scrollable--container`]: container,
11
+ },
12
+ ]"
13
+ role="navigation"
14
+ v-bind="$attrs"
15
+ @keydown.right.prevent.stop="onRight"
16
+ @keydown.left.prevent.stop="onLeft"
17
+ >
18
+ <button
19
+ aria-hidden="true"
20
+ aria-label="scroll left"
21
+ :class="[
22
+ {
23
+ [`${carbonPrefix}--tab--overflow-nav-button`]: horizontalOverflow,
24
+ [`${carbonPrefix}--tab--overflow-nav-button--hidden`]:
25
+ leftOverflowNavButtonHidden,
26
+ },
27
+ ]"
28
+ @click.stop.prevent="e => onOverflowClick(e, { direction: -1 })"
29
+ @mousedown.stop.prevent="e => onOverflowMouseDown(e, { direction: -1 })"
30
+ @mouseup.stop.prevent="onOverflowMouseUp"
31
+ tabIndex="-1"
32
+ type="button"
33
+ ref="elLeftOverflow"
34
+ >
35
+ <ChevronLeft16 />
36
+ </button>
37
+ <div
38
+ v-if="!leftOverflowNavButtonHidden"
39
+ :class="`${carbonPrefix}--tabs__overflow-indicator--left`"
40
+ />
41
+
42
+ <ul
43
+ :class="`${carbonPrefix}--tabs--scrollable__nav`"
44
+ role="tablist"
45
+ ref="elTabList"
46
+ >
47
+ <li
48
+ v-for="tab in tabs"
49
+ :key="tab.uid"
50
+ :class="[
51
+ `cv-tabs-button ${carbonPrefix}--tabs--scrollable__nav-item`,
52
+ {
53
+ [`${carbonPrefix}--tabs__nav-item--selected`]:
54
+ selectedId === tab.uid,
55
+ [`${carbonPrefix}--tabs__nav-item--disabled`]: disabledTabs.has(
56
+ tab.uid
57
+ ),
58
+ // TODO: remove scrollable in next major release
59
+ [`${carbonPrefix}--tabs--scrollable__nav-item--disabled`]:
60
+ disabledTabs.has(tab.uid),
61
+ [`${carbonPrefix}--tabs--scrollable__nav-item--selected`]:
62
+ selectedId === tab.uid,
63
+ },
64
+ ]"
65
+ role="presentation"
66
+ >
67
+ <button
68
+ :class="`${carbonPrefix}--tabs--scrollable__nav-link`"
69
+ role="tab"
70
+ :aria-controls="tab.uid"
71
+ :aria-disabled="disabledTabs.has(tab.uid)"
72
+ :aria-selected="selectedId === tab.uid"
73
+ :id="`${tab.uid}-link`"
74
+ @click="onTabClick(tab.uid)"
75
+ ref="elLink"
76
+ :tabindex="selectedId === tab.uid ? 0 : -1"
77
+ type="button"
78
+ >
79
+ {{ tab.label }}
80
+ </button>
81
+ </li>
82
+ </ul>
83
+
84
+ <div
85
+ v-if="!rightOverflowNavButtonHidden"
86
+ :class="`${carbonPrefix}--tabs__overflow-indicator--right`"
87
+ />
88
+ <button
89
+ aria-hidden="true"
90
+ aria-label="scroll right"
91
+ :class="[
92
+ {
93
+ [`${carbonPrefix}--tab--overflow-nav-button`]: horizontalOverflow,
94
+ [`${carbonPrefix}--tab--overflow-nav-button--hidden`]:
95
+ rightOverflowNavButtonHidden,
96
+ },
97
+ ]"
98
+ @click="e => onOverflowClick(e, { direction: 1 })"
99
+ @mousedown="e => onOverflowMouseDown(e, { direction: 1 })"
100
+ @mouseup="onOverflowMouseUp"
101
+ tabIndex="-1"
102
+ type="button"
103
+ ref="elRightOverflow"
104
+ >
105
+ <ChevronRight16 />
106
+ </button>
107
+ </div>
108
+ <div class="cv-tabs__panels">
109
+ <slot></slot>
110
+ </div>
111
+ </div>
112
+ </template>
113
+
114
+ <script setup>
115
+ import { carbonPrefix } from '../../global/settings';
116
+ import { ChevronLeft16, ChevronRight16 } from '@carbon/icons-vue';
117
+ import {
118
+ nextTick,
119
+ onBeforeUnmount,
120
+ onMounted,
121
+ onUpdated,
122
+ provide,
123
+ ref,
124
+ watch,
125
+ } from 'vue';
126
+
127
+ const OVERFLOW_BUTTON_OFFSET = 40;
128
+
129
+ const props = defineProps({
130
+ /**
131
+ * Changes the look of the tabs themselves. Does NOT change the styles of the tab content.
132
+ */
133
+ container: { type: Boolean, default: false },
134
+ /**
135
+ * @deprecated not used in component
136
+ */
137
+ leftOverflowIconButtonProps: Object,
138
+ /**
139
+ * This is an edge use case. By default, if no tab is explicitly selected, the first non-disabled tab is selected.
140
+ */
141
+ noDefaultToFirst: { type: Boolean, default: false },
142
+ /**
143
+ * @deprecated not used in component
144
+ */
145
+ rightOverflowIconButtonProps: Object,
146
+ /**
147
+ * @deprecated not used in component
148
+ */
149
+ scrollIntoView: { type: Boolean, default: true },
150
+ });
151
+ const emit = defineEmits(['tab-selected', 'tab-selected-id']);
152
+
153
+ const selectedId = ref('');
154
+ provide('tab-selected', selectedId);
155
+ /**
156
+ * @typedef TabData
157
+ * @type {string} uid
158
+ * @type {string} label
159
+ */
160
+
161
+ /**
162
+ * @type {Ref<UnwrapRef<Array<TabData>>>}
163
+ */
164
+ const tabs = ref([]);
165
+ provide('tab-data', tabs);
166
+ const disabledTabs = ref(new Set());
167
+ provide('tab-disabled-ids', disabledTabs);
168
+ const horizontalOverflow = ref(false);
169
+ const leftOverflowNavButtonHidden = ref(false);
170
+ const rightOverflowNavButtonHidden = ref(false);
171
+
172
+ function maybeSelectFirstTab() {
173
+ // Maybe user does not want something selected by default? Seems like this is an odd use case.
174
+ if (props.noDefaultToFirst) return;
175
+
176
+ // is something selected?
177
+ if (tabs.value.find(tab => tab.uid === selectedId.value)) return;
178
+
179
+ // Find the first not-disabled tab
180
+ const index = tabs.value?.findIndex(
181
+ tabData => !disabledTabs.value?.has(tabData.uid)
182
+ );
183
+
184
+ // if there is a non-disabled tab set that one as selected
185
+ if (index > -1) selectedId.value = tabs.value[index].uid;
186
+ }
187
+ onMounted(maybeSelectFirstTab);
188
+ watch(tabs, maybeSelectFirstTab);
189
+ watch(selectedId, () => {
190
+ nextTick(() => {
191
+ doTabClick(selectedId.value);
192
+ });
193
+ });
194
+ const elTabList = ref(null);
195
+ onMounted(() => {
196
+ window.addEventListener('resize', handleResize);
197
+ elTabList.value?.addEventListener('scroll', checkScroll);
198
+ checkScroll();
199
+ });
200
+
201
+ onUpdated(() => {
202
+ checkScroll();
203
+ });
204
+ onBeforeUnmount(() => {
205
+ window.removeEventListener('resize', handleResize);
206
+ elTabList.value?.removeEventListener('scroll', checkScroll);
207
+ });
208
+
209
+ const skipScroll = ref(false);
210
+ const elLink = ref(null);
211
+ function checkScroll() {
212
+ if (!skipScroll.value) {
213
+ if (elTabList.value) {
214
+ horizontalOverflow.value =
215
+ elTabList.value?.scrollWidth > elTabList.value?.clientWidth;
216
+
217
+ if (elLink.value && elLink.value.length > 0) {
218
+ leftOverflowNavButtonHidden.value = elTabList.value?.scrollLeft <= 0;
219
+ rightOverflowNavButtonHidden.value =
220
+ elTabList.value?.scrollLeft + elTabList.value?.clientWidth >=
221
+ elTabList.value?.scrollWidth;
222
+ }
223
+ }
224
+ }
225
+ }
226
+ function handleResize() {
227
+ checkScroll();
228
+ }
229
+
230
+ function doScrollIntoView(index) {
231
+ const tab = elLink.value[index];
232
+ /**
233
+ * HTMLElement
234
+ * @type {HTMLElement|null}
235
+ */
236
+ const scrollContainer =
237
+ tab && tab.parentNode ? tab.parentNode.parentNode : null;
238
+ let newScrollLeft;
239
+
240
+ if (tab && scrollContainer) {
241
+ const scrollLeft = scrollContainer.scrollLeft;
242
+ const tabLeft = tab.offsetLeft - scrollContainer.offsetLeft;
243
+
244
+ if (index === 0) {
245
+ newScrollLeft = 0;
246
+ } else if (index === elLink.value.length - 1) {
247
+ newScrollLeft = scrollContainer.scrollWidth - scrollContainer.offsetWidth;
248
+ } else if (tabLeft < scrollLeft) {
249
+ newScrollLeft = tabLeft;
250
+ } else {
251
+ const rightOfTab =
252
+ tab.offsetLeft - scrollContainer.offsetLeft + tab.offsetWidth;
253
+ if (rightOfTab > scrollContainer.offsetWidth + scrollLeft) {
254
+ newScrollLeft = rightOfTab - scrollContainer.offsetWidth;
255
+
256
+ if (!rightOverflowNavButtonHidden.value) {
257
+ newScrollLeft += OVERFLOW_BUTTON_OFFSET;
258
+ }
259
+ }
260
+ }
261
+ }
262
+
263
+ if (newScrollLeft !== undefined) {
264
+ skipScroll.value = true;
265
+ leftOverflowNavButtonHidden.value = newScrollLeft <= 0;
266
+ rightOverflowNavButtonHidden.value =
267
+ newScrollLeft + elTabList.value?.clientWidth >=
268
+ elTabList.value?.scrollWidth;
269
+
270
+ nextTick(() => {
271
+ // allow left and right nav hide to propagate before setting scroll
272
+ scrollContainer.scrollLeft = newScrollLeft;
273
+ skipScroll.value = false;
274
+ });
275
+ }
276
+ }
277
+ function doTabClick(id, setFocus = false) {
278
+ if (!disabledTabs.value.has(id)) {
279
+ if (selectedId.value !== id) {
280
+ let newIndex = -1;
281
+
282
+ selectedId.value = id;
283
+
284
+ for (let i = 0; i < tabs.value.length; i++) {
285
+ if (tabs.value[i].uid === id) {
286
+ doScrollIntoView(i, setFocus);
287
+ if (setFocus) {
288
+ nextTick(() => {
289
+ elLink.value[i].focus();
290
+ });
291
+ }
292
+ newIndex = i;
293
+ }
294
+ }
295
+
296
+ emit('tab-selected', newIndex);
297
+ emit('tab-selected-id', tabs.value[newIndex].uid);
298
+ }
299
+ }
300
+ }
301
+ function onTabClick(id) {
302
+ doTabClick(id);
303
+ }
304
+ function isAllTabsDisabled() {
305
+ return disabledTabs.value.size === tabs.value.length;
306
+ }
307
+ function onLeft() {
308
+ if (isAllTabsDisabled()) {
309
+ return;
310
+ }
311
+
312
+ const curIndex = move(selectedId.value, false);
313
+ const newId = tabs.value[curIndex].uid;
314
+
315
+ doTabClick(newId, true);
316
+ }
317
+ function move(id, next) {
318
+ let newIndex;
319
+ let newId;
320
+
321
+ newIndex = tabs.value.findIndex(tab => tab.uid === id);
322
+
323
+ if (newIndex > -1) {
324
+ newIndex = next ? newIndex + 1 : newIndex - 1;
325
+ }
326
+ if (newIndex < 0 || newIndex >= tabs.value.length) {
327
+ newIndex = next ? 0 : tabs.value.length - 1;
328
+ }
329
+
330
+ newId = tabs.value[newIndex].uid;
331
+ while (newId === selectedId.value || disabledTabs.value.has(newId)) {
332
+ if (newIndex > -1) {
333
+ newIndex = next ? newIndex + 1 : newIndex - 1;
334
+ }
335
+ if (newIndex < 0 || newIndex >= tabs.value.length) {
336
+ newIndex = next ? 0 : tabs.value.length - 1;
337
+ }
338
+ newId = tabs.value[newIndex].uid;
339
+ }
340
+
341
+ return newIndex;
342
+ }
343
+ function onRight() {
344
+ if (isAllTabsDisabled()) {
345
+ return;
346
+ }
347
+
348
+ const curIndex = move(selectedId.value, true);
349
+ const newId = tabs.value[curIndex].uid;
350
+
351
+ doTabClick(newId, true);
352
+ }
353
+ const elRightOverflow = ref(null);
354
+ const elLeftOverflow = ref(null);
355
+ function onOverflowClick(e, { direction, multiplier = 10 }) {
356
+ const scrollLeft = elTabList.value?.scrollLeft;
357
+
358
+ // account for overflow button appearing and causing tablist width change
359
+ if (elTabList.value && direction === 1 && !scrollLeft) {
360
+ elTabList.value.scrollLeft += OVERFLOW_BUTTON_OFFSET;
361
+ }
362
+
363
+ if (elTabList.value) elTabList.value.scrollLeft += direction * multiplier;
364
+
365
+ if (elTabList.value && leftEdgeReached(direction)) {
366
+ elTabList.value.scrollLeft = 0;
367
+ }
368
+
369
+ // account for reaching left edge
370
+ if (leftEdgeReached(direction)) {
371
+ elRightOverflow.value?.focus();
372
+ }
373
+ if (rightEdgeReached(direction)) {
374
+ elLeftOverflow.value?.focus();
375
+ }
376
+ }
377
+ let overflowNavInterval;
378
+ function onOverflowMouseDown(e, { direction }) {
379
+ // disregard mouse buttons aside from LMB
380
+ if (e.buttons !== 1) {
381
+ return;
382
+ }
383
+
384
+ if (overflowNavInterval) clearInterval(overflowNavInterval);
385
+ overflowNavInterval = setInterval(() => {
386
+ if (leftEdgeReached(direction) || rightEdgeReached(direction)) {
387
+ clearInterval(overflowNavInterval);
388
+ }
389
+
390
+ onOverflowClick(e, { direction });
391
+ });
392
+ }
393
+ function onOverflowMouseUp() {
394
+ clearInterval(overflowNavInterval);
395
+ }
396
+ function leftEdgeReached(direction) {
397
+ const scrollLeft = elTabList.value?.scrollLeft || Number.MIN_VALUE;
398
+
399
+ return direction === -1 && scrollLeft <= OVERFLOW_BUTTON_OFFSET;
400
+ }
401
+ function rightEdgeReached(direction) {
402
+ if (!elTabList.value) return false;
403
+ const { clientWidth, scrollLeft, scrollWidth } = elTabList.value;
404
+ return direction === 1 && scrollLeft + clientWidth >= scrollWidth;
405
+ }
406
+ </script>
@@ -0,0 +1,24 @@
1
+ <!--suppress ALL -->
2
+ <template>
3
+ <div
4
+ :class="`cv-tabs ${carbonPrefix}--tabs ${carbonPrefix}--skeleton ${carbonPrefix}--tabs--scrollable`"
5
+ >
6
+ <ul :class="`${carbonPrefix}--tabs--scrollable__nav`">
7
+ <li
8
+ v-for="index in tabCount"
9
+ :key="index"
10
+ :class="`${carbonPrefix}--tabs--scrollable__nav-item`"
11
+ >
12
+ <div :class="`${carbonPrefix}--tabs__nav-link`"><span></span></div>
13
+ </li>
14
+ </ul>
15
+ </div>
16
+ </template>
17
+
18
+ <script setup>
19
+ import { carbonPrefix } from '../../global/settings';
20
+
21
+ defineProps({
22
+ tabCount: { type: Number, default: 5 },
23
+ });
24
+ </script>
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <div class="sb-tabs-container">
3
+ <slot></slot>
4
+ </div>
5
+ </template>
6
+
7
+ <script setup></script>
8
+
9
+ <style>
10
+ .bx--tabs--container ~ div {
11
+ min-height: 320px;
12
+ background-color: #e8daff;
13
+ background-repeat: no-repeat;
14
+ background-position: center;
15
+ }
16
+ .bx--tabs--container ~ .cv-tabs__panels:after {
17
+ content: 'Container style is not from carbon. This example uses ".bx--tabs--container ~ div" selector to add the "#e8daff" background color.';
18
+ opacity: 0.3;
19
+ font-size: 30pt;
20
+ z-index: -10;
21
+ }
22
+ </style>
@@ -0,0 +1,5 @@
1
+ import CvTabs from './CvTabs.vue';
2
+ import CvTab from './CvTab.vue';
3
+ import CvTabsSkeleton from './CvTabsSkeleton.vue';
4
+
5
+ export { CvTab, CvTabs, CvTabsSkeleton };
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <div :class="`cv-toggle ${carbonPrefix}--form-item`" :id="cvId">
3
+ <label
4
+ :class="`${carbonPrefix}--toggle__label ${carbonPrefix}--skeleton`"
5
+ :for="cvId"
6
+ :aria-label="label ? 'Toggle is loading' : label"
7
+ >
8
+ <span :class="`${carbonPrefix}--toggle__label-text`" />
9
+ <span :class="`${carbonPrefix}--toggle__text--left`" />
10
+ <span :class="`${carbonPrefix}--toggle__appearance`" />
11
+ <span :class="`${carbonPrefix}--toggle__text--right`" />
12
+ </label>
13
+ </div>
14
+ </template>
15
+
16
+ <script setup>
17
+ import { carbonPrefix } from '../../global/settings';
18
+ import { useCvId, props as propsCvId } from '../../use/cvId';
19
+
20
+ const props = defineProps({
21
+ label: {
22
+ type: String,
23
+ default: 'Toggle is loading',
24
+ ...propsCvId,
25
+ },
26
+ });
27
+
28
+ const cvId = useCvId(props);
29
+ </script>