@vcita/design-system 1.8.1 → 1.9.0-beta.1

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 (48) hide show
  1. package/config/locales/ds.en.yml +6 -0
  2. package/dist/@vcita/design-system.esm.js +107 -48
  3. package/dist/@vcita/design-system.min.js +2 -2
  4. package/dist/@vcita/design-system.ssr.js +115 -56
  5. package/init/DesignSystem.js +5 -0
  6. package/init/vuetify.config.js +1 -0
  7. package/package.json +8 -3
  8. package/src/components/VcCheckbox/VcCheckbox.vue +1 -1
  9. package/src/components/VcGroupedItems/VcGroupedItems.vue +58 -32
  10. package/src/components/VcSearchLayout/VcSearchLayout.spec.js +217 -0
  11. package/src/components/VcSearchLayout/VcSearchLayout.stories.js +160 -0
  12. package/src/components/VcSearchLayout/VcSearchLayout.vue +181 -0
  13. package/src/components/VcTabs/VcTabs.vue +1 -1
  14. package/src/components/VcTextArea/VcTextArea.vue +1 -0
  15. package/src/components/VcTextField/VcTextField.vue +10 -0
  16. package/src/components/VcToast/VcToast.vue +6 -0
  17. package/src/components/sidepane/VcClientItem/VcClientItem.spec.js +23 -0
  18. package/src/components/sidepane/VcClientItem/VcClientItem.vue +4 -4
  19. package/src/components/sidepane/VcDetailItem/VcDetailItem.stories.js +2 -0
  20. package/src/components/sidepane/VcDetailItem/VcDetailItem.vue +9 -1
  21. package/src/filters/highlightFilter.js +9 -0
  22. package/src/scss/global.scss +6 -0
  23. package/src/stories/Configure.mdx +364 -0
  24. package/src/stories/assets/accessibility.png +0 -0
  25. package/src/stories/assets/accessibility.svg +5 -0
  26. package/src/stories/assets/addon-library.png +0 -0
  27. package/src/stories/assets/assets.png +0 -0
  28. package/src/stories/assets/context.png +0 -0
  29. package/src/stories/assets/discord.svg +15 -0
  30. package/src/stories/assets/docs.png +0 -0
  31. package/src/stories/assets/figma-plugin.png +0 -0
  32. package/src/stories/assets/github.svg +3 -0
  33. package/src/stories/assets/pics/advertising_thumbnail.svg +6 -0
  34. package/src/stories/assets/pics/booking_thumbnail.svg +4 -0
  35. package/src/stories/assets/pics/calendar_thumbnail.svg +4 -0
  36. package/src/stories/assets/pics/clients_thumbnail.svg +4 -0
  37. package/src/stories/assets/pics/marketing_thumbnail.svg +4 -0
  38. package/src/stories/assets/pics/payment_thumbnail.svg +5 -0
  39. package/src/stories/assets/pics/team_chat_thumbnail.svg +6 -0
  40. package/src/stories/assets/share.png +0 -0
  41. package/src/stories/assets/styling.png +0 -0
  42. package/src/stories/assets/testing.png +0 -0
  43. package/src/stories/assets/theming.png +0 -0
  44. package/src/stories/assets/tutorials.svg +12 -0
  45. package/src/stories/assets/youtube.svg +4 -0
  46. package/src/stories/button.css +30 -0
  47. package/src/stories/header.css +32 -0
  48. package/src/stories/page.css +69 -0
@@ -1,27 +1,32 @@
1
1
  <template>
2
2
  <VcLayout column :data-qa="dataQa" class="grouped-items">
3
3
  <div class="grouped-items__group"
4
+ :class="{[groupClass]: groupClass}"
4
5
  v-for="(group, groupIndex) in itemGroups"
5
6
  :key="group.id"
6
7
  :data-qa="`group-${group.id}`">
7
- <div :class="`grouped-items__group__header ${flavor} mb-2`" >
8
- <slot :name="`group-${group.id}-header`" :group="group">
9
- {{group.label}}
8
+ <slot :name="`group-${group.id}-header`" :group="group">
9
+ <slot name="header" :group="group">
10
+ <div :class="`grouped-items__group__header ${flavor} mb-2`">
11
+ {{ group.label }}
12
+ </div>
10
13
  </slot>
11
- </div>
14
+ </slot>
12
15
  <VcLayout column :class="`grouped-items__group__container ${flavor}`">
13
16
  <VcLayout @click="$emit('change',item.id)"
14
- :data-qa="`item-${item.id}`"
15
- v-if="group.items"
16
- v-for="(item) in group.items"
17
- :key="item.id"
18
- :class="{selected: item.id === selected, disabled: item.disabled, mobile: isMobile, 'rtl': $vuetify.rtl}"
19
- class="grouped-items__group__container__menu-item d-flex align-center flex-grow-1">
17
+ :data-qa="`item-${item.id}`"
18
+ v-if="group.items"
19
+ v-for="(item) in group.items"
20
+ :key="item.id"
21
+ :class="{selected: item.id === selected, disabled: item.disabled, mobile: isMobile, 'rtl': $vuetify.rtl, [itemClass]: itemClass}"
22
+ class="grouped-items__group__container__menu-item d-flex align-center flex-grow-1">
20
23
  <slot :name="`item-${item.id}`" :item="item">
21
- <VcLayout justify-space-between align-center class="px-4 flex-grow-1">
22
- <span>{{item.label}}</span>
23
- <vcIcon size="12" v-if="isMobile">$chevron_right</vcIcon>
24
- </VcLayout>
24
+ <slot name="item" :item="item">
25
+ <VcLayout justify-space-between align-center class="px-4 flex-grow-1">
26
+ <span>{{ item.label }}</span>
27
+ <vcIcon size="12" v-if="isMobile">$chevron_right</vcIcon>
28
+ </VcLayout>
29
+ </slot>
25
30
  </slot>
26
31
  </VcLayout>
27
32
  </VcLayout>
@@ -59,6 +64,12 @@ export default {
59
64
  showDividers: {
60
65
  type: Boolean,
61
66
  default: false,
67
+ },
68
+ groupClass: {
69
+ type: String,
70
+ },
71
+ itemClass: {
72
+ type: String,
62
73
  }
63
74
  },
64
75
  model: {
@@ -66,7 +77,7 @@ export default {
66
77
  event: 'change'
67
78
  },
68
79
  computed: {
69
- isMobile(){
80
+ isMobile() {
70
81
  return !this.$vuetify.breakpoint.mdAndUp
71
82
  }
72
83
  }
@@ -75,9 +86,10 @@ export default {
75
86
 
76
87
  <style lang="scss" scoped>
77
88
  .grouped-items {
78
- gap: var(--size-value5);
79
- &__group{
80
- &__header{
89
+ column-gap: var(--size-value5);
90
+
91
+ &__group {
92
+ &__header {
81
93
  font-size: var(--font-size-xx-small);
82
94
  font-weight: var(--font-weight-large);
83
95
  line-height: var(--size-value5);
@@ -88,13 +100,16 @@ export default {
88
100
  font-size: var(--font-size-xxx-small);
89
101
  }
90
102
  }
91
- &__container{
103
+
104
+ &__container {
92
105
  border-radius: var(--border-radius);
93
106
  background-color: var(--modal-bg-color);
107
+
94
108
  &.elevated {
95
109
  box-shadow: var(--shadow-1);
96
110
  }
97
- &__menu-item{
111
+
112
+ &__menu-item {
98
113
  cursor: pointer;
99
114
  box-sizing: border-box;
100
115
  height: var(--size-value11);
@@ -103,46 +118,57 @@ export default {
103
118
  border-inline-start: 3px solid var(--modal-bg-color);
104
119
  transition: 0.3s;
105
120
  transition-property: border-color, background-color;
106
- &:first-of-type{
121
+
122
+ &:first-of-type {
107
123
  margin-top: var(--size-value1);
108
124
  }
109
- &:last-of-type{
125
+
126
+ &:last-of-type {
110
127
  margin-bottom: var(--size-value1);
111
128
  }
112
- &:hover{
129
+
130
+ &:hover {
113
131
  border-color: var(--gray-lighten-3);
114
132
  background-color: var(--gray-lighten-3);
115
133
  }
116
- &:focus{
134
+
135
+ &:focus {
117
136
  border: 3px solid var(--v-secondary-lighten1);
118
137
  }
119
- &:active:not(.mobile){
138
+
139
+ &:active:not(.mobile) {
120
140
  border-color: var(--gray-lighten-1);
121
141
  background-color: var(--gray-lighten-1);
122
142
  }
123
- &.selected{
143
+
144
+ &.selected {
124
145
  font-weight: var(--font-weight-large);
125
146
  }
126
- &.selected:not(.mobile){
147
+
148
+ &.selected:not(.mobile) {
127
149
  border-inline-start: 3px solid var(--v-secondary-base);
128
150
  }
129
- &.disabled{
151
+
152
+ &.disabled {
130
153
  cursor: unset;
131
154
  pointer-events: none;
132
155
  border-color: var(--gray-lighten-3);
133
156
  background-color: var(--gray-lighten-3);
134
157
  color: var(--gray-darken-2);
135
158
  }
136
- &.mobile{
159
+
160
+ &.mobile {
137
161
  color: var(--gray-darken-5);
138
162
  height: var(--size-value13);
139
- ::v-deep{
163
+
164
+ ::v-deep {
140
165
  svg.vc-chevron-right {
141
166
  fill: var(--gray-darken-2);
142
167
  }
143
168
  }
144
- &.rtl{
145
- ::v-deep{
169
+
170
+ &.rtl {
171
+ ::v-deep {
146
172
  svg.vc-chevron-right {
147
173
  transform: scaleX(-1);
148
174
  }
@@ -0,0 +1,217 @@
1
+ import '@testing-library/jest-dom'
2
+ import VcSearchLayout from "./VcSearchLayout.vue";
3
+ import Vue from 'vue'
4
+ import Vuetify from 'vuetify'
5
+ import {render} from "@testing-library/vue";
6
+ import init from "../../../testing-library.config";
7
+ import userEvent from "@testing-library/user-event";
8
+
9
+ init();
10
+
11
+ Vue.use(Vuetify)
12
+
13
+ const itemGroupsInput = [
14
+ {
15
+ label: 'CLIENTS (1)',
16
+ id: 1,
17
+ items: [
18
+ {id: '1', name: 'Allan blair', colorId: 1, details: 'allan_blair2002@gmail.com'},
19
+ ]
20
+ },
21
+ {
22
+ label: 'BOOKING (1)',
23
+ id: 2,
24
+ items: [
25
+ {
26
+ id: '2', name: 'Appointment', details: 'Allan Blair | Sep 20, 2021',
27
+ imagePath: require('@/stories/assets/pics/booking_thumbnail.svg')
28
+ },
29
+ ]
30
+ },
31
+ {
32
+ label: 'PAYMENTS (8)',
33
+ id: 3,
34
+ items: [
35
+ {
36
+ id: '3', name: 'Record payment', details: 'Allan Blair | Sep 20, 2021',
37
+ imagePath: require('@/stories/assets/pics/payment_thumbnail.svg')
38
+ },
39
+ {
40
+ id: '4', name: 'Estimate #0000122', details: 'Allan Blair | Sep 20, 2021',
41
+ imagePath: require('@/stories/assets/pics/payment_thumbnail.svg')
42
+ },
43
+ {
44
+ id: '5', name: '<div>Record payment</div>', details: 'Allan Blair | Sep 20, 2021',
45
+ imagePath: require('@/stories/assets/pics/payment_thumbnail.svg')
46
+ },
47
+ {
48
+ id: 'showMore', showMore: true,
49
+ },
50
+ ]
51
+ },
52
+ {
53
+ label: 'DOCUMENTS (1)',
54
+ id: 4,
55
+ items: [
56
+ {
57
+ id: '7', name: 'Contract AB.pdf', details: 'Allan Blair | Sep 20, 2021',
58
+ imagePath: require('@/stories/assets/pics/marketing_thumbnail.svg')
59
+ },
60
+ ]
61
+ },
62
+ {
63
+ label: 'CONVERSATION (1)',
64
+ id: 5,
65
+ items: [
66
+ {
67
+ id: '8', name: 'Message sent', details: 'Allan Blair | Sep 20, 2021',
68
+ imagePath: require('@/stories/assets/pics/clients_thumbnail.svg')
69
+ },
70
+ ]
71
+ },
72
+ ];
73
+
74
+ const baseProps = {
75
+ label: 'Search with results',
76
+ value: false,
77
+ itemGroups: itemGroupsInput,
78
+ isLoading: false,
79
+ emptyStateTitle: undefined,
80
+ emptyStateSubtitle: undefined,
81
+ emptyStateImage: require('@/stories/assets/rabbit.svg'),
82
+ dataQa: 'VcSearchLayout',
83
+ };
84
+
85
+ describe("VcSearchLayout.vue", () => {
86
+
87
+ const renderWithVuetify = (component, options, callback, isMobile = false) => {
88
+ const root = document.createElement('div')
89
+ root.setAttribute('data-app', 'true')
90
+
91
+ const vuetify = new Vuetify()
92
+ if (isMobile) {
93
+ const breakpoint = {
94
+ init: jest.fn(),
95
+ framework: {},
96
+ smAndDown: true,
97
+ }
98
+
99
+ vuetify.framework.breakpoint = breakpoint;
100
+ }
101
+
102
+ return render(
103
+ component,
104
+ {
105
+ container: document.body.appendChild(root),
106
+ // for Vuetify components that use the vuetify instance property
107
+ vuetify,
108
+ ...options,
109
+ mocks: {
110
+ $t: value => value,
111
+ $dst: value => value, // <- for the design system
112
+ },
113
+ },
114
+ callback,
115
+ )
116
+ }
117
+
118
+ it("displays basic items", () => {
119
+ const {container, getByTestId} = renderWithVuetify(VcSearchLayout, {
120
+ props: {
121
+ ...baseProps,
122
+ groupItems: undefined,
123
+ }
124
+ });
125
+
126
+ // Expect options: https://github.com/testing-library/jest-dom
127
+ expect(container).toHaveAttribute('data-app', 'true');
128
+
129
+ expect(getByTestId('VcSearchLayout-searchBar')).toBeInTheDocument();
130
+ });
131
+
132
+ it("displays item groups", () => {
133
+ // Queries: https://testing-library.com/docs/queries/about#types-of-queries
134
+ const {getByTestId} = renderWithVuetify(VcSearchLayout, {
135
+ props: {
136
+ ...baseProps,
137
+ value: true
138
+ }
139
+ });
140
+
141
+ // Make sure the component has the data-qa attribute
142
+ expect(getByTestId('VcSearchLayout-content')).toBeInTheDocument();
143
+ expect(getByTestId('VcSearchLayout-footer')).toBeInTheDocument();
144
+ expect(getByTestId('VcSearchLayout-groupedItems')).toBeInTheDocument();
145
+ });
146
+
147
+ it("opens and closes based on value", async () => {
148
+ const {getByTestId, updateProps, queryByTestId} = renderWithVuetify(VcSearchLayout, {
149
+ props: {
150
+ ...baseProps,
151
+ itemGroups: undefined,
152
+ value: true
153
+ }
154
+ });
155
+
156
+ expect(getByTestId('VcSearchLayout-content')).toBeInTheDocument();
157
+ await updateProps({value: false});
158
+ expect(queryByTestId('VcSearchLayout-content')).not.toBeVisible();
159
+ });
160
+
161
+ it("clicks work", async () => {
162
+ const {getByText, getByTestId, emitted} = renderWithVuetify(VcSearchLayout, {
163
+ props: {
164
+ ...baseProps,
165
+ value: true,
166
+ }
167
+ });
168
+
169
+ // show more is clicked
170
+ const showMore = getByText('ds.search_layout.show_more');
171
+ await userEvent.click(showMore);
172
+ expect(emitted().onItemClicked.length).toBe(1);
173
+ console.log(JSON.stringify(baseProps.itemGroups[2].items[3].id));
174
+ expect(emitted().onItemClicked).toStrictEqual([[baseProps.itemGroups[2].items[3].id]]);
175
+
176
+ // show all is clicked
177
+ const showAll = getByText('ds.search_layout.view_all');
178
+ await userEvent.click(showAll);
179
+ expect(emitted().onSeeAllClicked.length).toBe(1);
180
+
181
+ const inputField = getByTestId('VcSearchLayout-searchBar');
182
+ await userEvent.type(inputField, 'a');
183
+ expect(emitted().onSearch.length).toBe(1);
184
+ expect(emitted().onSearch[0]).toEqual(['a']);
185
+
186
+ const searchItem = getByTestId('item-2');
187
+ await userEvent.click(searchItem);
188
+ expect(emitted().onItemClicked.length).toBe(2);
189
+ expect(emitted().onItemClicked[1]).toEqual(["2"]);
190
+ });
191
+
192
+ it("loading state", () => {
193
+ const {getByTestId} = renderWithVuetify(VcSearchLayout, {
194
+ props: {
195
+ ...baseProps,
196
+ value: true,
197
+ isLoading: true,
198
+ },
199
+ });
200
+
201
+ const loader = getByTestId('VcLoader');
202
+ expect(loader).toBeInTheDocument();
203
+ });
204
+
205
+ it("empty state", () => {
206
+ const {getByTestId} = renderWithVuetify(VcSearchLayout, {
207
+ props: {
208
+ ...baseProps,
209
+ groupItems: undefined,
210
+ value: true,
211
+ itemGroups: undefined,
212
+ }
213
+ });
214
+
215
+ expect(getByTestId('VcEmptyState')).toBeInTheDocument();
216
+ });
217
+ });
@@ -0,0 +1,160 @@
1
+ import VcSearchLayoutCmp from './VcSearchLayout';
2
+ import VcBaseDocs from '@/stories/VcBaseDocs.mdx'
3
+
4
+ const itemGroupsInput = [
5
+ {
6
+ label: 'CLIENTS (1)',
7
+ id: 1,
8
+ items: [
9
+ {id: '1', name: 'Allan blair', colorId: 1, details: 'allan_blair2002@gmail.com'},
10
+ ]
11
+ },
12
+ {
13
+ label: 'BOOKING (1)',
14
+ id: 2,
15
+ items: [
16
+ {
17
+ id: '2', name: 'Appointment', details: 'Allan Blair | Sep 20, 2021',
18
+ imagePath: require('@/stories/assets/pics/booking_thumbnail.svg')
19
+ },
20
+ ]
21
+ },
22
+ {
23
+ label: 'PAYMENTS (8)',
24
+ id: 3,
25
+ items: [
26
+ {
27
+ id: '3', name: 'Record payment', details: 'Allan Blair | Sep 20, 2021',
28
+ imagePath: require('@/stories/assets/pics/payment_thumbnail.svg')
29
+ },
30
+ {
31
+ id: '4', name: 'Estimate #0000122', details: 'Allan Blair | Sep 20, 2021',
32
+ imagePath: require('@/stories/assets/pics/payment_thumbnail.svg')
33
+ },
34
+ {
35
+ id: '5', name: '<div>Record payment</div>', details: 'Allan Blair | Sep 20, 2021',
36
+ imagePath: require('@/stories/assets/pics/payment_thumbnail.svg')
37
+ },
38
+ {
39
+ id: 'showMore', showMore: true,
40
+ },
41
+ ]
42
+ },
43
+ {
44
+ label: 'DOCUMENTS (1)',
45
+ id: 4,
46
+ items: [
47
+ {
48
+ id: '7', name: 'Contract AB.pdf', details: 'Allan Blair | Sep 20, 2021',
49
+ imagePath: require('@/stories/assets/pics/marketing_thumbnail.svg')
50
+ },
51
+ ]
52
+ },
53
+ {
54
+ label: 'CONVERSATION (1)',
55
+ id: 5,
56
+ items: [
57
+ {
58
+ id: '8', name: 'Message sent', details: 'Allan Blair | Sep 20, 2021',
59
+ imagePath: require('@/stories/assets/pics/clients_thumbnail.svg')
60
+ },
61
+ ]
62
+ },
63
+ ];
64
+
65
+ const baseProps = {
66
+ label: 'Search with results',
67
+ isLoading: false,
68
+ emptyStateTitle: undefined,
69
+ emptyStateSubtitle: undefined,
70
+ emptyStateImage: require('@/stories/assets/rabbit.svg'),
71
+ dataQa: 'VcSearchLayout',
72
+ };
73
+
74
+ const Template = (args, {argTypes}) => ({
75
+ components: {VcSearchLayout: VcSearchLayoutCmp},
76
+ props: Object.keys(argTypes),
77
+ data: () => ({
78
+ itemGroups: [],
79
+ value: true,
80
+ }),
81
+ template: `
82
+ <div>You can pass here any props you would pass to VcSearchBar <br>
83
+ Enter any text to see results, Enter 'al' to see highlighting
84
+ <VcSearchLayout :value="value"
85
+ :label="label"
86
+ :dataQa="dataQa"
87
+ :itemGroups="itemGroups"
88
+ :is-loading="isLoading"
89
+ :empty-state-title="emptyStateTitle"
90
+ :empty-state-subtitle="emptyStateSubtitle"
91
+ :empty-state-image="emptyStateImage"
92
+ @onDropdownChange="onDropdownChange"
93
+ @onSearch="onInput"
94
+ @onItemClicked="onItemClicked"
95
+ @onSeeAllClicked="onSeeAllClicked"/>
96
+ </div>`,
97
+ methods: {
98
+ onInput(value) {
99
+ this.onSearch(value);
100
+ if (value.length > 0) {
101
+ this.itemGroups = itemGroupsInput;
102
+ this.value = true;
103
+ }
104
+ else {
105
+ this.itemGroups = undefined;
106
+ this.value = false;
107
+ }
108
+ }
109
+ }
110
+ })
111
+
112
+ export const Playground = Template.bind({});
113
+
114
+ // Set default values
115
+ Playground.args = {
116
+ ...baseProps,
117
+ }
118
+
119
+ export const Loading = Template.bind({});
120
+
121
+ // Set default values
122
+ Loading.args = {
123
+ ...baseProps,
124
+ isLoading: true,
125
+ value: true,
126
+ itemGroups: undefined,
127
+ }
128
+ export const EmptyState = Template.bind({});
129
+
130
+ // Set default values
131
+ EmptyState.args = {
132
+ ...baseProps,
133
+ value: true,
134
+ itemGroups: undefined,
135
+ }
136
+
137
+ export default {
138
+ title: 'desktop layout / VcSearchLayout', // This will control the story sidebar position
139
+ id: 'VcSearchLayout', // This will be the permanent link to this component
140
+ component: VcSearchLayoutCmp,
141
+ argTypes: {
142
+ label: {table: {category: 'Search Bar props'}},
143
+ onSearch: {table: {category: 'Search Bar event'}},
144
+ onItemClicked: {table: {category: 'Grouped Items event'}},
145
+ },
146
+ parameters: {
147
+ design: {
148
+ type: 'figma',
149
+ url: 'https://www.figma.com/file/lso6l9nvwhOn2snCxg9VZK/New-Layout-(MVP)?type=design&node-id=1684-134993&mode=design&t=K13zOU49FG5nAWWh-0',
150
+ },
151
+ status: {
152
+ type: 'beta', // 'beta' | 'stable' | 'deprecated' | 'releaseCandidate'
153
+ },
154
+ docs: {
155
+ page: VcBaseDocs,
156
+ inlineStories: false,
157
+ iframeHeight: 700,
158
+ },
159
+ },
160
+ };