@mozaic-ds/vue 1.0.0-beta.7 → 1.0.0-beta.9

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 (67) hide show
  1. package/README.md +1 -1
  2. package/dist/mozaic-vue.css +1 -1
  3. package/dist/mozaic-vue.d.ts +605 -210
  4. package/dist/mozaic-vue.js +1281 -629
  5. package/dist/mozaic-vue.js.map +1 -1
  6. package/dist/mozaic-vue.umd.cjs +1 -1
  7. package/dist/mozaic-vue.umd.cjs.map +1 -1
  8. package/package.json +8 -11
  9. package/src/components/Contributing.mdx +1 -1
  10. package/src/components/GettingStarted.mdx +2 -7
  11. package/src/components/Introduction.mdx +41 -21
  12. package/src/components/Support.mdx +1 -1
  13. package/src/components/breadcrumb/MBreadcrumb.stories.ts +11 -13
  14. package/src/components/breadcrumb/MBreadcrumb.vue +1 -1
  15. package/src/components/button/MButton.stories.ts +1 -8
  16. package/src/components/checkbox/MCheckbox.stories.ts +2 -2
  17. package/src/components/checkboxgroup/MCheckboxGroup.stories.ts +2 -2
  18. package/src/components/divider/MDivider.spec.ts +57 -0
  19. package/src/components/divider/MDivider.stories.ts +64 -0
  20. package/src/components/divider/MDivider.vue +56 -0
  21. package/src/components/drawer/MDrawer.spec.ts +100 -0
  22. package/src/components/drawer/MDrawer.stories.ts +128 -0
  23. package/src/components/drawer/MDrawer.vue +140 -0
  24. package/src/components/field/MField.stories.ts +2 -9
  25. package/src/components/fieldgroup/MFieldGroup.stories.ts +2 -9
  26. package/src/components/iconbutton/MIconButton.stories.ts +12 -4
  27. package/src/components/link/MLink.stories.ts +3 -12
  28. package/src/components/loader/MLoader.stories.ts +3 -5
  29. package/src/components/loader/MLoader.vue +1 -0
  30. package/src/components/loadingoverlay/MLoadingOverlay.spec.ts +37 -0
  31. package/src/components/loadingoverlay/MLoadingOverlay.stories.ts +40 -0
  32. package/src/components/loadingoverlay/MLoadingOverlay.vue +28 -0
  33. package/src/components/modal/MModal.spec.ts +103 -0
  34. package/src/components/modal/MModal.stories.ts +127 -0
  35. package/src/components/modal/MModal.vue +131 -0
  36. package/src/components/numberbadge/MNumberBadge.stories.ts +3 -5
  37. package/src/components/overlay/MOverlay.stories.ts +3 -8
  38. package/src/components/pagination/MPagination.spec.ts +123 -0
  39. package/src/components/pagination/MPagination.stories.ts +83 -0
  40. package/src/components/pagination/MPagination.vue +142 -0
  41. package/src/components/passwordinput/MPasswordInput.stories.ts +2 -2
  42. package/src/components/passwordinput/MPasswordInput.vue +2 -5
  43. package/src/components/pincode/MPincode.spec.ts +126 -0
  44. package/src/components/pincode/MPincode.stories.ts +68 -0
  45. package/src/components/pincode/MPincode.vue +139 -0
  46. package/src/components/quantityselector/MQuantitySelector.stories.ts +2 -2
  47. package/src/components/radio/MRadio.stories.ts +2 -2
  48. package/src/components/radiogroup/MRadioGroup.stories.ts +2 -2
  49. package/src/components/select/MSelect.stories.ts +2 -2
  50. package/src/components/statusbadge/MStatusBadge.stories.ts +1 -1
  51. package/src/components/statusdot/MStatusDot.stories.ts +1 -1
  52. package/src/components/statusnotification/MStatusNotification.spec.ts +12 -8
  53. package/src/components/statusnotification/MStatusNotification.stories.ts +2 -9
  54. package/src/components/statusnotification/MStatusNotification.vue +8 -8
  55. package/src/components/tabs/MTabs.stories.ts +104 -0
  56. package/src/components/tabs/MTabs.vue +113 -0
  57. package/src/components/tabs/Mtabs.spec.ts +149 -0
  58. package/src/components/tag/MTag.spec.ts +107 -0
  59. package/src/components/tag/MTag.stories.ts +75 -0
  60. package/src/components/tag/MTag.vue +151 -0
  61. package/src/components/textarea/MTextArea.stories.ts +2 -2
  62. package/src/components/textinput/MTextInput.stories.ts +2 -9
  63. package/src/components/toggle/MToggle.stories.ts +2 -2
  64. package/src/components/togglegroup/MToggleGroup.stories.ts +2 -2
  65. package/src/components/usingIcons.mdx +5 -13
  66. package/src/components/usingPresets.mdx +12 -9
  67. package/src/main.ts +8 -0
@@ -0,0 +1,127 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite';
2
+ import { ref, watch } from 'vue';
3
+ import { action } from 'storybook/actions';
4
+ import MModal from './MModal.vue';
5
+ import MButton from '../button/MButton.vue';
6
+ import MLink from '../link/MLink.vue';
7
+ import InfoCircle32 from '@mozaic-ds/icons-vue/src/components/InfoCircle32/InfoCircle32.vue';
8
+ import ExternalLink24 from '@mozaic-ds/icons-vue/src/components/ExternalLink24/ExternalLink24.vue';
9
+
10
+ const meta: Meta<typeof MModal> = {
11
+ title: 'Overlay/Modal',
12
+ component: MModal,
13
+ parameters: {
14
+ layout: 'fullscreen',
15
+ docs: {
16
+ story: { height: '400px' },
17
+ description: {
18
+ component:
19
+ 'A modal is a dialog window that appears on top of the main content, requiring user interaction before returning to the main interface. It is used to focus attention on a specific task, provide important information, or request confirmation for an action. Modals typically include a title, description, and primary/secondary actions and should be used for single, focused tasks to avoid disrupting the user experience.',
20
+ },
21
+ },
22
+ },
23
+ args: {
24
+ open: true,
25
+ title: 'Modal title',
26
+ description: `A modal is a dialog window allowing you to focus the user's attention on a specific task, a piece of information or a mandatory action. It must be used for single action only and must have a call to action button in the bottom.`,
27
+ footer: `
28
+ <MButton ghost>Cancel</MButton>
29
+ <MButton>Primary action</MButton>
30
+ `,
31
+ },
32
+ render: (args) => ({
33
+ components: { MModal, MButton, MLink, InfoCircle32, ExternalLink24 },
34
+ setup() {
35
+ const handleUpdate = action('update:open');
36
+ const openState = ref(args.open);
37
+
38
+ watch(
39
+ () => args.open,
40
+ (val) => {
41
+ openState.value = val;
42
+ },
43
+ );
44
+
45
+ const onUpdateOpen = (val: boolean) => {
46
+ openState.value = val;
47
+ handleUpdate(val);
48
+ args.open = val;
49
+ };
50
+
51
+ return { args, openState, onUpdateOpen };
52
+ },
53
+ template: `
54
+ <MModal
55
+ v-bind="args"
56
+ @update:open="onUpdateOpen"
57
+ v-model:open="openState"
58
+ >
59
+ <template v-if="${'icon' in args}" v-slot:icon>${args.icon}</template>
60
+ <template v-if="${'default' in args}" v-slot>${args.default}</template>
61
+ <template v-if="${'link' in args}" v-slot:link>${args.link}</template>
62
+ <template v-if="${'footer' in args}" v-slot:footer>${args.footer}</template>
63
+ </MModal>
64
+ `,
65
+ }),
66
+ };
67
+ export default meta;
68
+ type Story = StoryObj<typeof MModal>;
69
+
70
+ export const Default: Story = {};
71
+
72
+ export const Icon = {
73
+ args: {
74
+ icon: '<InfoCircle32/>',
75
+ },
76
+ };
77
+
78
+ export const ValidationOnly = {
79
+ args: {
80
+ footer: `
81
+ <MButton>Primary action</MButton>
82
+ `,
83
+ },
84
+ };
85
+
86
+ export const TwoOptions = {
87
+ args: {
88
+ footer: `
89
+ <MButton outlined>Secondary action</MButton>
90
+ <MButton>Primary action</MButton>
91
+ `,
92
+ },
93
+ };
94
+
95
+ export const Cancel = {
96
+ args: {
97
+ footer: `
98
+ <MButton ghost>Cancel</MButton>
99
+ <MButton outlined>Secondary action</MButton>
100
+ <MButton>Primary action</MButton>
101
+ `,
102
+ },
103
+ };
104
+
105
+ export const Link = {
106
+ args: {
107
+ link: `
108
+ <MLink class="mc-modal__link" href="#" size="m" icon-position="right">
109
+ Learn more
110
+ <template #icon><ExternalLink24 /></template>
111
+ </MLink>
112
+ `,
113
+ footer: `
114
+ <MButton ghost>Cancel</MButton>
115
+ <MButton>Primary action</MButton>
116
+ `,
117
+ },
118
+ };
119
+
120
+ export const Danger = {
121
+ args: {
122
+ footer: `
123
+ <MButton ghost>Cancel</MButton>
124
+ <MButton appearance="danger">Primary action</MButton>
125
+ `,
126
+ },
127
+ };
@@ -0,0 +1,131 @@
1
+ <template>
2
+ <MOverlay :is-visible="open" dialogLabel="modalTitle">
3
+ <section
4
+ class="mc-modal"
5
+ :class="classObject"
6
+ role="dialog"
7
+ aria-labelledby="modalTitle"
8
+ :aria-modal="open ? 'true' : 'false'"
9
+ tabindex="-1"
10
+ :aria-hidden="!open"
11
+ v-bind="$attrs"
12
+ @keydown.esc="onClose"
13
+ >
14
+ <div class="mc-modal__dialog" role="document">
15
+ <header class="mc-modal__header">
16
+ <span v-if="$slots.icon" class="mc-modal__icon">
17
+ <slot name="icon" />
18
+ </span>
19
+ <h2 class="mc-modal__title" id="modalTitle">
20
+ {{ title }}
21
+ </h2>
22
+ <MIconButton
23
+ v-if="closable"
24
+ class="mc-modal__close"
25
+ aria-label="Close"
26
+ ghost
27
+ @click="onClose"
28
+ >
29
+ <template #icon>
30
+ <Cross24 aria-hidden="true" />
31
+ </template>
32
+ </MIconButton>
33
+ </header>
34
+ <main class="mc-modal__body">
35
+ <p>{{ description }}</p>
36
+ <slot />
37
+ </main>
38
+ <footer v-if="$slots.footer" class="mc-modal__footer">
39
+ <span class="mc-modal__link">
40
+ <slot name="link" />
41
+ </span>
42
+ <slot name="footer" />
43
+ </footer>
44
+ </div>
45
+ </section>
46
+ </MOverlay>
47
+ </template>
48
+
49
+ <script setup lang="ts">
50
+ import { computed, watch, type VNode } from 'vue';
51
+ import Cross24 from '@mozaic-ds/icons-vue/src/components/Cross24/Cross24.vue';
52
+ import MIconButton from '../iconbutton/MIconButton.vue';
53
+ import MOverlay from '../overlay/MOverlay.vue';
54
+ /**
55
+ * A modal is a dialog window that appears on top of the main content, requiring user interaction before returning to the main interface. It is used to focus attention on a specific task, provide important information, or request confirmation for an action. Modals typically include a title, description, and primary/secondary actions and should be used for single, focused tasks to avoid disrupting the user experience.
56
+ */
57
+ const props = withDefaults(
58
+ defineProps<{
59
+ /**
60
+ * if `true`, display the modal.
61
+ */
62
+ open?: boolean;
63
+ /**
64
+ * Title of the modal
65
+ */
66
+ title: string;
67
+ /**
68
+ * Description of the modal
69
+ */
70
+ description?: string;
71
+ /**
72
+ * if `true`, display the close button.
73
+ */
74
+ closable?: boolean;
75
+ }>(),
76
+ {
77
+ closable: true,
78
+ },
79
+ );
80
+
81
+ defineSlots<{
82
+ /**
83
+ * Use this slot to insert an icon next to the title of the modal
84
+ */
85
+ icon?: VNode;
86
+ /**
87
+ * Use this slot to insert the content of the modal
88
+ */
89
+ default?: VNode;
90
+ /**
91
+ * Use this slot to insert a link in the footer
92
+ */
93
+ link?: VNode;
94
+ /**
95
+ * Use this slot to insert buttons in the footer
96
+ */
97
+ footer?: VNode;
98
+ }>();
99
+
100
+ const classObject = computed(() => {
101
+ return {
102
+ 'is-open': props.open,
103
+ };
104
+ });
105
+
106
+ watch(
107
+ () => props.open,
108
+ (newValue) => {
109
+ emit('update:open', newValue);
110
+ },
111
+ );
112
+
113
+ const onClose = () => {
114
+ emit('update:open', false);
115
+ };
116
+
117
+ const emit = defineEmits<{
118
+ /**
119
+ * Emits when the checkbox value changes, updating the modelValue prop.
120
+ */
121
+ (on: 'update:open', value: boolean): void;
122
+ }>();
123
+ </script>
124
+
125
+ <style lang="scss" scoped>
126
+ @use '@mozaic-ds/styles/components/modal';
127
+
128
+ .mc-overlay {
129
+ filter: none;
130
+ }
131
+ </style>
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/vue3';
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite';
2
2
  import MNumberBadge from './MNumberBadge.vue';
3
3
 
4
4
  const meta: Meta<typeof MNumberBadge> = {
@@ -37,10 +37,8 @@ export const Danger: Story = {
37
37
  };
38
38
 
39
39
  export const Inverse: Story = {
40
- parameters: {
41
- backgrounds: {
42
- default: 'Inverse',
43
- },
40
+ globals: {
41
+ backgrounds: { value: 'inverse' },
44
42
  },
45
43
  args: { appearance: 'inverse' },
46
44
  };
@@ -1,11 +1,13 @@
1
- import type { Meta, StoryObj } from '@storybook/vue3';
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite';
2
2
  import MOverlay from './MOverlay.vue';
3
3
 
4
4
  const meta: Meta<typeof MOverlay> = {
5
5
  title: 'Overlay/Overlay',
6
6
  component: MOverlay,
7
7
  parameters: {
8
+ layout: 'fullscreen',
8
9
  docs: {
10
+ story: { height: '400px' },
9
11
  description: {
10
12
  component:
11
13
  'An overlay component is a UI element that appears above the main content to display additional information or interactions, often blocking or dimming the background.',
@@ -15,13 +17,6 @@ const meta: Meta<typeof MOverlay> = {
15
17
  args: {
16
18
  isVisible: true,
17
19
  },
18
- argTypes: {
19
- $slots: {
20
- table: {
21
- disable: true,
22
- },
23
- },
24
- },
25
20
  render: (args) => ({
26
21
  components: { MOverlay },
27
22
  setup() {
@@ -0,0 +1,123 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import MPagination from './MPagination.vue';
4
+
5
+ const options = [
6
+ { value: 1, text: 'Page 1' },
7
+ { value: 2, text: 'Page 2' },
8
+ { value: 3, text: 'Page 3' },
9
+ ];
10
+
11
+ describe('MPagination component', () => {
12
+ it('renders correctly with select when compact is false', () => {
13
+ const wrapper = mount(MPagination, {
14
+ props: {
15
+ id: 'test-pagination',
16
+ modelValue: 2,
17
+ options,
18
+ compact: false,
19
+ selectLabel: 'Select page',
20
+ },
21
+ });
22
+
23
+ // Check buttons rendered as MButton (not MIconButton)
24
+ expect(wrapper.findAllComponents({ name: 'MButton' }).length).toBe(2);
25
+ expect(wrapper.findAllComponents({ name: 'MIconButton' }).length).toBe(0);
26
+
27
+ // Check select is present
28
+ expect(wrapper.find('select').exists()).toBe(true);
29
+ });
30
+
31
+ it('renders icon buttons only when compact is true', () => {
32
+ const wrapper = mount(MPagination, {
33
+ props: {
34
+ id: 'test-pagination',
35
+ modelValue: 2,
36
+ options,
37
+ compact: true,
38
+ },
39
+ });
40
+
41
+ // MIconButton rendered for previous and next
42
+ expect(wrapper.findAllComponents({ name: 'MIconButton' }).length).toBe(2);
43
+ // No MButton when compact
44
+ expect(wrapper.findAllComponents({ name: 'MButton' }).length).toBe(0);
45
+
46
+ // No select rendered
47
+ expect(wrapper.find('select').exists()).toBe(false);
48
+ });
49
+
50
+ it('disables previous button if on first page', async () => {
51
+ const wrapper = mount(MPagination, {
52
+ props: {
53
+ id: 'test-pagination',
54
+ modelValue: 1,
55
+ options,
56
+ },
57
+ });
58
+ const prevButton = wrapper.findAllComponents({ name: 'MButton' })[0];
59
+ expect(prevButton.attributes('disabled')).toBeDefined();
60
+ });
61
+
62
+ it('disables next button if on last page', async () => {
63
+ const wrapper = mount(MPagination, {
64
+ props: {
65
+ id: 'test-pagination',
66
+ modelValue: 3,
67
+ options,
68
+ },
69
+ });
70
+ const buttons = wrapper.findAllComponents({ name: 'MButton' });
71
+ const nextButton = buttons[buttons.length - 1];
72
+ expect(nextButton.attributes('disabled')).toBeDefined();
73
+ });
74
+
75
+ it('clicking previous button updates the modelValue', async () => {
76
+ const wrapper = mount(MPagination, {
77
+ props: {
78
+ id: 'test-pagination',
79
+ modelValue: 2,
80
+ options,
81
+ },
82
+ });
83
+
84
+ await wrapper.findAllComponents({ name: 'MButton' })[0].trigger('click');
85
+ // Should emit update:modelValue with value 1 (previous page)
86
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy();
87
+ expect(wrapper.emitted('update:modelValue')![0]).toEqual([1]);
88
+ });
89
+
90
+ it('clicking next button updates the modelValue', async () => {
91
+ const wrapper = mount(MPagination, {
92
+ props: {
93
+ id: 'test-pagination',
94
+ modelValue: 2,
95
+ options,
96
+ },
97
+ });
98
+
99
+ const buttons = wrapper.findAllComponents({ name: 'MButton' });
100
+ await buttons[buttons.length - 1].trigger('click');
101
+
102
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy();
103
+ expect(wrapper.emitted('update:modelValue')![0]).toEqual([3]);
104
+ });
105
+
106
+ it('emits update:modelValue when select value changes', async () => {
107
+ const wrapper = mount(MPagination, {
108
+ props: {
109
+ id: 'test-pagination',
110
+ modelValue: 1,
111
+ options,
112
+ compact: false,
113
+ selectLabel: 'Select page',
114
+ },
115
+ });
116
+
117
+ const select = wrapper.find('select');
118
+ await select.setValue('3');
119
+
120
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy();
121
+ expect(wrapper.emitted('update:modelValue')![0]).toEqual([3]);
122
+ });
123
+ });
@@ -0,0 +1,83 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite';
2
+ import { action } from 'storybook/actions';
3
+
4
+ import MPagination from './MPagination.vue';
5
+
6
+ const meta: Meta<typeof MPagination> = {
7
+ title: 'Navigation/Pagination',
8
+ component: MPagination,
9
+ parameters: {
10
+ docs: {
11
+ description: {
12
+ component:
13
+ 'Pagination is a navigation component that allows users to browse through large sets of content by dividing it into discrete pages. It typically includes previous and next buttons, numeric page selectors, or dropdowns to jump between pages efficiently. Pagination improves usability and performance in content-heavy applications such as tables, search results, and articles by preventing long scrolls and reducing page load times.',
14
+ },
15
+ },
16
+ },
17
+ args: {
18
+ id: 'paginationId',
19
+ modelValue: 10,
20
+ options: [
21
+ {
22
+ text: 'Page 1 of 99',
23
+ value: 1,
24
+ },
25
+ {
26
+ text: 'Page 2 of 99',
27
+ value: 2,
28
+ },
29
+ {
30
+ text: 'Page 3 of 99',
31
+ value: 3,
32
+ },
33
+ {
34
+ text: 'Page 10 of 99',
35
+ value: 10,
36
+ },
37
+ {
38
+ text: 'Page 99 of 99',
39
+ value: 99,
40
+ },
41
+ ],
42
+ selectLabel: 'Select page',
43
+ },
44
+ render: (args) => ({
45
+ components: { MPagination },
46
+ setup() {
47
+ const handleUpdate = action('update:modelValue');
48
+
49
+ return { args, handleUpdate };
50
+ },
51
+ template: `
52
+ <MPagination
53
+ v-bind="args"
54
+ @update:modelValue="handleUpdate"
55
+ />
56
+ `,
57
+ }),
58
+ };
59
+ export default meta;
60
+ type Story = StoryObj<typeof MPagination>;
61
+
62
+ export const Default: Story = {};
63
+
64
+ export const First: Story = {
65
+ args: {
66
+ id: 'firstId',
67
+ modelValue: 1,
68
+ },
69
+ };
70
+
71
+ export const Last: Story = {
72
+ args: {
73
+ id: 'lastId',
74
+ modelValue: 99,
75
+ },
76
+ };
77
+
78
+ export const Compact: Story = {
79
+ args: {
80
+ id: 'compactId',
81
+ compact: true,
82
+ },
83
+ };
@@ -0,0 +1,142 @@
1
+ <template>
2
+ <nav class="mc-pagination" role="navigation" aria-label="pagination">
3
+ <MButton
4
+ v-if="!compact"
5
+ icon-position="only"
6
+ aria-label="Previous page"
7
+ :disabled="isFirstPage"
8
+ @click="previous"
9
+ >
10
+ <template #icon><ChevronLeft24 /></template>
11
+ </MButton>
12
+ <MIconButton
13
+ v-else
14
+ outlined
15
+ aria-label="Previous page"
16
+ :disabled="isFirstPage"
17
+ @click="previous"
18
+ >
19
+ <template #icon><ChevronLeft24 /></template>
20
+ </MIconButton>
21
+
22
+ <div v-if="!compact" class="mc-pagination__field">
23
+ <MSelect
24
+ class="mc-pagination__select"
25
+ :id="id"
26
+ v-model="currentValue"
27
+ :options="options"
28
+ @update:model-value="emit('update:modelValue', Number($event))"
29
+ :aria-label="selectLabel"
30
+ ></MSelect>
31
+ </div>
32
+
33
+ <span v-if="compact" class="mc-pagination__label" aria-current="page">
34
+ {{ options.find((option) => option.value === currentValue)?.text }}
35
+ </span>
36
+
37
+ <MButton
38
+ v-if="!compact"
39
+ icon-position="only"
40
+ aria-label="Next page"
41
+ :disabled="isLastPage"
42
+ @click="next"
43
+ >
44
+ <template #icon><ChevronRight24 /></template>
45
+ </MButton>
46
+ <MIconButton
47
+ v-else
48
+ outlined
49
+ aria-label="Next page"
50
+ :disabled="isLastPage"
51
+ @click="next"
52
+ >
53
+ <template #icon><ChevronRight24 /></template>
54
+ </MIconButton>
55
+ </nav>
56
+ </template>
57
+
58
+ <script setup lang="ts">
59
+ import { computed, ref, watch } from 'vue';
60
+ import MButton from '../button/MButton.vue';
61
+ import MSelect from '../select/MSelect.vue';
62
+ import ChevronLeft24 from '@mozaic-ds/icons-vue/src/components/ChevronLeft24/ChevronLeft24.vue';
63
+ import ChevronRight24 from '@mozaic-ds/icons-vue/src/components/ChevronRight24/ChevronRight24.vue';
64
+ import MIconButton from '../iconbutton/MIconButton.vue';
65
+ /**
66
+ * Pagination is a navigation component that allows users to browse through large sets of content by dividing it into discrete pages. It typically includes previous and next buttons, numeric page selectors, or dropdowns to jump between pages efficiently. Pagination improves usability and performance in content-heavy applications such as tables, search results, and articles by preventing long scrolls and reducing page load times.
67
+ */
68
+ const props = defineProps<{
69
+ /**
70
+ * A unique identifier for the pagination.
71
+ */
72
+ id: string;
73
+ /**
74
+ * The current value of the selected page.
75
+ */
76
+ modelValue: number;
77
+ /**
78
+ * If `true`, display a compact version without the select.
79
+ */
80
+ compact?: boolean;
81
+ /**
82
+ * Define the available choices for the pagination select element.
83
+ */
84
+ options: Array<{
85
+ id?: string;
86
+ text: string;
87
+ value: number;
88
+ }>;
89
+ /**
90
+ * Accessible label for the select of the pagination.
91
+ */
92
+ selectLabel?: string;
93
+ }>();
94
+
95
+ const emit = defineEmits<{
96
+ /**
97
+ * Emits when the pagination value changes, updating the modelValue prop.
98
+ */
99
+ (on: 'update:modelValue', value: number): void;
100
+ }>();
101
+
102
+ const currentValue = ref(props.modelValue);
103
+
104
+ watch(currentValue, (newVal) => {
105
+ if (newVal !== props.modelValue) {
106
+ emit('update:modelValue', newVal);
107
+ }
108
+ });
109
+
110
+ const currentIndex = computed(() =>
111
+ props.options.findIndex((opt) => opt.value === currentValue.value),
112
+ );
113
+
114
+ const isFirstPage = computed(() => currentIndex.value === 0);
115
+ const isLastPage = computed(
116
+ () => currentIndex.value === props.options.length - 1,
117
+ );
118
+
119
+ const previous = () => {
120
+ const currentIndex = props.options.findIndex(
121
+ (opt) => opt.value === currentValue.value,
122
+ );
123
+ if (currentIndex > 0) {
124
+ currentValue.value = props.options[currentIndex - 1].value;
125
+ emit('update:modelValue', props.options[currentIndex - 1].value);
126
+ }
127
+ };
128
+
129
+ const next = () => {
130
+ const currentIndex = props.options.findIndex(
131
+ (opt) => opt.value === currentValue.value,
132
+ );
133
+ if (currentIndex < props.options.length - 1) {
134
+ currentValue.value = props.options[currentIndex + 1].value;
135
+ emit('update:modelValue', props.options[currentIndex + 1].value);
136
+ }
137
+ };
138
+ </script>
139
+
140
+ <style lang="scss" scoped>
141
+ @use '@mozaic-ds/styles/components/pagination';
142
+ </style>
@@ -1,5 +1,5 @@
1
- import type { Meta, StoryObj } from '@storybook/vue3';
2
- import { action } from '@storybook/addon-actions';
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite';
2
+ import { action } from 'storybook/actions';
3
3
 
4
4
  import MPasswordInput from './MPasswordInput.vue';
5
5
 
@@ -16,10 +16,7 @@
16
16
  "
17
17
  />
18
18
  <div v-if="isClearable && modelValue" class="mc-controls-options">
19
- <button
20
- class="mc-controls-options__button"
21
- @click="clearValue"
22
- >
19
+ <button class="mc-controls-options__button" @click="clearValue">
23
20
  <CrossCircleFilled24
24
21
  class="mc-controls-options__icon"
25
22
  aria-hidden="true"
@@ -33,7 +30,7 @@
33
30
  :aria-checked="ariaChecked"
34
31
  :disabled="disabled"
35
32
  @click="toggleVisibility"
36
- size="s"
33
+ size="s"
37
34
  ghost
38
35
  >
39
36
  {{ isVisible ? buttonLabel.hide : buttonLabel.show }}