sprintify-ui 0.0.11 → 0.0.13

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 (96) hide show
  1. package/README.md +8 -7
  2. package/dist/sprintify-ui.es.js +5911 -3760
  3. package/dist/style.css +1 -1
  4. package/dist/tailwindcss/index.js +1 -2
  5. package/dist/types/src/components/BaseCharacterCounter.vue.d.ts +143 -0
  6. package/dist/types/src/components/BaseHasMany.vue.d.ts +277 -0
  7. package/dist/types/src/components/BaseInput.vue.d.ts +39 -5
  8. package/dist/types/src/components/BaseLoadingCover.vue.d.ts +72 -0
  9. package/dist/types/src/components/{BaseMediaLibraryItem.vue.d.ts → BaseMediaItem.vue.d.ts} +26 -4
  10. package/dist/types/src/components/BaseMediaLibrary.vue.d.ts +23 -15
  11. package/dist/types/src/components/BaseMediaPreview.vue.d.ts +97 -0
  12. package/dist/types/src/components/BaseModalCenter.vue.d.ts +8 -8
  13. package/dist/types/src/components/BaseModalSide.vue.d.ts +8 -8
  14. package/dist/types/src/components/BasePagination.vue.d.ts +105 -13
  15. package/dist/types/src/components/BaseSelect.vue.d.ts +130 -26
  16. package/dist/types/src/components/BaseSideNavigationItem.vue.d.ts +20 -1
  17. package/dist/types/src/components/BaseSwitch.vue.d.ts +15 -8
  18. package/dist/types/src/components/BaseTabItem.vue.d.ts +45 -4
  19. package/dist/types/src/components/BaseTagAutocomplete.vue.d.ts +25 -17
  20. package/dist/types/src/components/BaseTagAutocompleteFetch.vue.d.ts +37 -21
  21. package/dist/types/src/components/BaseTextareaAutoresize.vue.d.ts +175 -21
  22. package/dist/types/src/components/index.d.ts +30 -1
  23. package/dist/types/src/index.d.ts +4 -0
  24. package/package.json +1 -1
  25. package/src/components/BaseAppDialogs.vue +2 -2
  26. package/src/components/BaseAppNotifications.vue +1 -1
  27. package/src/components/BaseAutocomplete.vue +16 -18
  28. package/src/components/BaseBelongsTo.vue +1 -0
  29. package/src/components/BaseCharacterCounter.stories.js +30 -0
  30. package/src/components/BaseCharacterCounter.vue +60 -0
  31. package/src/components/BaseClipboard.vue +1 -1
  32. package/src/components/BaseDataIterator.stories.js +2 -2
  33. package/src/components/BaseDataIterator.vue +32 -38
  34. package/src/components/BaseDataTable.stories.js +2 -2
  35. package/src/components/BaseFileUploader.vue +4 -0
  36. package/src/components/BaseHasMany.vue +92 -0
  37. package/src/components/BaseInput.stories.js +46 -0
  38. package/src/components/BaseInput.vue +10 -2
  39. package/src/components/BaseInputLabel.stories.js +31 -0
  40. package/src/components/BaseInputLabel.vue +1 -1
  41. package/src/components/BaseLoadingCover.stories.js +55 -0
  42. package/src/components/BaseLoadingCover.vue +19 -1
  43. package/src/components/BaseMediaItem.stories.js +41 -0
  44. package/src/components/BaseMediaItem.vue +71 -0
  45. package/src/components/BaseMediaLibrary.stories.js +80 -0
  46. package/src/components/BaseMediaLibrary.vue +67 -68
  47. package/src/components/BaseMediaPreview.stories.js +72 -0
  48. package/src/components/BaseMediaPreview.vue +90 -0
  49. package/src/components/BaseMenu.stories.js +125 -0
  50. package/src/components/BaseMenu.vue +1 -1
  51. package/src/components/BaseModalCenter.stories.js +61 -0
  52. package/src/components/BaseModalCenter.vue +2 -2
  53. package/src/components/BaseModalSide.stories.js +55 -0
  54. package/src/components/BaseModalSide.vue +2 -2
  55. package/src/components/BaseNavbar.stories.js +150 -0
  56. package/src/components/BaseNavbar.vue +3 -0
  57. package/src/components/BaseNavbarItem.vue +1 -0
  58. package/src/components/BaseNavbarItemContent.vue +3 -0
  59. package/src/components/BasePagination.stories.js +32 -0
  60. package/src/components/BasePagination.vue +126 -40
  61. package/src/components/BasePanel.stories.js +56 -0
  62. package/src/components/BasePassword.stories.js +36 -0
  63. package/src/components/BasePassword.vue +11 -5
  64. package/src/components/BaseProcessRing.stories.js +27 -0
  65. package/src/components/BaseReadMore.stories.js +30 -0
  66. package/src/components/BaseReadMore.vue +1 -1
  67. package/src/components/BaseSelect.stories.js +67 -0
  68. package/src/components/BaseSelect.vue +144 -44
  69. package/src/components/BaseSideNavigation.stories.js +55 -0
  70. package/src/components/BaseSideNavigation.vue +7 -2
  71. package/src/components/BaseSideNavigationItem.vue +21 -5
  72. package/src/components/BaseSkeleton.stories.js +36 -0
  73. package/src/components/BaseSwitch.stories.js +101 -0
  74. package/src/components/BaseSwitch.vue +90 -12
  75. package/src/components/BaseSystemAlert.stories.js +63 -0
  76. package/src/components/BaseTabItem.vue +29 -6
  77. package/src/components/BaseTable.vue +2 -2
  78. package/src/components/BaseTabs.stories.js +54 -0
  79. package/src/components/BaseTabs.vue +3 -3
  80. package/src/components/BaseTagAutocomplete.stories.js +129 -0
  81. package/src/components/BaseTagAutocomplete.vue +155 -57
  82. package/src/components/BaseTagAutocompleteFetch.stories.js +130 -0
  83. package/src/components/BaseTagAutocompleteFetch.vue +36 -25
  84. package/src/components/BaseTextarea.stories.js +35 -0
  85. package/src/components/BaseTextarea.vue +1 -1
  86. package/src/components/BaseTextareaAutoresize.stories.js +49 -0
  87. package/src/components/BaseTextareaAutoresize.vue +83 -87
  88. package/src/components/HasMany.stories.js +135 -0
  89. package/src/components/index.ts +58 -0
  90. package/src/lang/en.json +2 -1
  91. package/src/lang/fr.json +2 -1
  92. package/dist/types/src/components/BasePaginationSimple.vue.d.ts +0 -25
  93. package/dist/types/src/components/BaseWordCount.vue.d.ts +0 -31
  94. package/src/components/BaseMediaLibraryItem.vue +0 -92
  95. package/src/components/BasePaginationSimple.vue +0 -60
  96. package/src/components/BaseWordCount.vue +0 -36
@@ -0,0 +1,130 @@
1
+ import BaseTagAutocompleteFetch from './BaseTagAutocompleteFetch.vue';
2
+ import BaseApp from './BaseApp.vue';
3
+
4
+ export default {
5
+ title: 'Form/BaseTagAutocompleteFetch',
6
+ component: BaseTagAutocompleteFetch,
7
+ argTypes: {},
8
+ args: {
9
+ url: 'https://effettandem.com/api/content/articles',
10
+ labelKey: 'title',
11
+ valueKey: 'id',
12
+ disabled: false,
13
+ },
14
+ decorators: [() => ({ template: '<div class="mb-36"><story/></div>' })],
15
+ };
16
+
17
+ const Template = (args) => {
18
+ return {
19
+ components: { BaseTagAutocompleteFetch, BaseApp },
20
+ setup() {
21
+ const value = ref([]);
22
+ return { args, value };
23
+ },
24
+ template: `
25
+ <BaseTagAutocompleteFetch
26
+ v-model="value"
27
+ v-bind="args"
28
+ ></BaseTagAutocompleteFetch>
29
+ <p class="mt-5 text-sm">Value: <span class="bg-slate-200 font-mono px-1 py-px rounded">{{ value ?? 'NULL' }}</span></p>
30
+ <BaseApp />
31
+ `,
32
+ };
33
+ };
34
+
35
+ export const Demo = Template.bind({});
36
+ Demo.args = {};
37
+
38
+ export const Disabled = Template.bind({});
39
+ Disabled.args = {
40
+ modelValue: [{ label: 'Dark Maul', value: '1' }],
41
+ labelKey: 'label',
42
+ valueKey: 'value',
43
+ disabled: true,
44
+ };
45
+
46
+ export const Maximum = Template.bind({});
47
+ Maximum.args = {
48
+ max: 3,
49
+ };
50
+
51
+ export const SlotOption = (args) => {
52
+ return {
53
+ components: { BaseTagAutocompleteFetch },
54
+ setup() {
55
+ const value = ref([]);
56
+ return { args, value };
57
+ },
58
+ template: `
59
+ <div class="mb-20">
60
+ <BaseTagAutocompleteFetch
61
+ v-model="value"
62
+ v-bind="args"
63
+ >
64
+ <template #option="{ option, active, selected }">
65
+ <div
66
+ class="rounded px-2 py-1"
67
+ :class="{
68
+ 'hover:bg-slate-100': !active && !selected,
69
+ 'bg-slate-200 hover:bg-slate-300': active && !selected,
70
+ 'bg-blue-500 text-white hover:bg-blue-600': !active && selected,
71
+ 'bg-blue-600 text-white hover:bg-blue-700': active && selected,
72
+ }"
73
+ >
74
+ <p class="text-sm font-medium">{{ option.title }}</p>
75
+ <p class="opacity-60 text-xs">{{ option.owner?.name }}</p>
76
+ </div>
77
+ </template>
78
+ </BaseTagAutocompleteFetch>
79
+ </div>
80
+ `,
81
+ };
82
+ };
83
+
84
+ export const SlotFooter = (args) => {
85
+ return {
86
+ components: { BaseTagAutocompleteFetch },
87
+ setup() {
88
+ const value = ref([]);
89
+ function onClick() {
90
+ alert(1);
91
+ }
92
+ return { args, value, onClick };
93
+ },
94
+ template: `
95
+ <BaseTagAutocompleteFetch
96
+ v-model="value"
97
+ v-bind="args"
98
+ >
99
+ <template #footer>
100
+ <div class="text-center p-2 border-t">
101
+ <button @click=onClick class="btn btn-sm w-full btn-slate-200-outline">This is the footer 💯</button>
102
+ </div>
103
+ </template>
104
+ </BaseTagAutocompleteFetch>
105
+ `,
106
+ };
107
+ };
108
+
109
+ export const SlotEmpty = (args) => {
110
+ return {
111
+ components: { BaseTagAutocompleteFetch },
112
+ setup() {
113
+ const value = ref([]);
114
+ return { args, value };
115
+ },
116
+ template: `
117
+ <BaseTagAutocompleteFetch
118
+ v-model="value"
119
+ v-bind="args"
120
+ >
121
+ <template #empty="props">
122
+ <div>
123
+ <div v-if="props.firstSearch" class="text-center py-10 p-6">🤓🤓🤓</div>
124
+ <div v-else class="text-center p-6">Start your search... 🔎</div>
125
+ </div>
126
+ </template>
127
+ </BaseTagAutocompleteFetch>
128
+ `,
129
+ };
130
+ };
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <BaseTagAutocomplete
3
- :loading="loading && page == 1"
3
+ :loading="showLoading && page == 1"
4
4
  :model-value="modelValue"
5
5
  :disabled="disabled"
6
6
  :placeholder="placeholder"
@@ -8,22 +8,30 @@
8
8
  :value-key="valueKey"
9
9
  :label-key="labelKey"
10
10
  :input-class="inputClass"
11
- :min="min"
12
11
  :max="max"
12
+ :filter="() => true"
13
13
  @focus="onFocus"
14
14
  @typing="onTyping"
15
15
  @scroll-bottom="scrollBottom"
16
16
  @update:model-value="$emit('update:modelValue', $event)"
17
17
  >
18
- <template #empty>
19
- <div class="p-5 text-center text-slate-600">
20
- <span v-if="firstSearch">
18
+ <template #option="optionProps">
19
+ <slot name="option" v-bind="optionProps" />
20
+ </template>
21
+
22
+ <template #footer="footerProps">
23
+ <slot name="footer" v-bind="footerProps" :keywords="keywords"> </slot>
24
+ </template>
25
+
26
+ <template #empty="emptyProps">
27
+ <slot name="empty" v-bind="emptyProps" :first-search="firstSearch">
28
+ <div
29
+ v-if="firstSearch"
30
+ class="flex h-[80px] items-center justify-center px-3 text-center text-base leading-tight text-slate-600"
31
+ >
21
32
  {{ $t('sui.nothing_found') }}
22
- </span>
23
- <span v-else>
24
- {{ $t('sui.autocomplete_placeholder') }}
25
- </span>
26
- </div>
33
+ </div>
34
+ </slot>
27
35
  </template>
28
36
  </BaseTagAutocomplete>
29
37
  </template>
@@ -33,6 +41,7 @@ import { debounce } from 'lodash';
33
41
  import { config } from '@/index';
34
42
  import { PropType, Ref } from 'vue';
35
43
  import { Option } from '@/types/types';
44
+ import BaseTagAutocomplete from './BaseTagAutocomplete.vue';
36
45
 
37
46
  const props = defineProps({
38
47
  modelValue: {
@@ -67,21 +76,22 @@ const props = defineProps({
67
76
  default: false,
68
77
  type: Boolean,
69
78
  },
70
- min: {
71
- default: undefined,
72
- type: Number,
73
- },
74
79
  max: {
75
80
  default: undefined,
76
81
  type: Number,
77
82
  },
83
+ queryKey: {
84
+ default: 'search',
85
+ type: String,
86
+ },
78
87
  });
79
88
 
80
89
  defineEmits(['update:modelValue', 'typing', 'focus', 'scrollBottom']);
81
90
 
82
91
  const http = config.http;
83
92
 
84
- const loading = ref(false);
93
+ const showLoading = ref(false);
94
+ const fetching = ref(false);
85
95
  const firstSearch = ref(false);
86
96
  const reachedEnd = ref(false);
87
97
  const keywords = ref('');
@@ -94,18 +104,15 @@ const onTyping = (query: string) => {
94
104
 
95
105
  if (keywords.value != query) {
96
106
  keywords.value = query;
107
+ showLoading.value = true;
97
108
  debouncedSearch();
98
109
  }
99
110
  };
100
111
 
101
112
  const onFocus = () => {
102
- if (firstSearch.value) {
103
- return;
113
+ if (!firstSearch.value) {
114
+ search();
104
115
  }
105
-
106
- search();
107
-
108
- firstSearch.value = true;
109
116
  };
110
117
 
111
118
  const scrollBottom = () => {
@@ -116,17 +123,18 @@ const scrollBottom = () => {
116
123
  };
117
124
 
118
125
  const search = () => {
119
- if (loading.value) {
126
+ if (fetching.value) {
120
127
  return;
121
128
  }
122
129
 
123
- loading.value = true;
130
+ fetching.value = true;
131
+ showLoading.value = true;
124
132
  firstSearch.value = true;
125
133
 
126
134
  http
127
135
  .get(props.url, {
128
136
  params: {
129
- search: keywords.value,
137
+ [props.queryKey]: keywords.value,
130
138
  page: page.value,
131
139
  },
132
140
  })
@@ -142,9 +150,12 @@ const search = () => {
142
150
  } else {
143
151
  options.value.push(...data);
144
152
  }
153
+
154
+ firstSearch.value = true;
145
155
  })
146
156
  .finally(() => {
147
- loading.value = false;
157
+ fetching.value = false;
158
+ showLoading.value = false;
148
159
  });
149
160
  };
150
161
 
@@ -0,0 +1,35 @@
1
+ import BaseTextarea from './BaseTextarea.vue';
2
+
3
+ export default {
4
+ title: 'Form/BaseTextarea',
5
+ component: BaseTextarea,
6
+ args: {
7
+ name: 'bio',
8
+ required: true,
9
+ placeholder: 'Describe your complete life in 4 sentences...',
10
+ },
11
+ };
12
+
13
+ const Template = (args) => ({
14
+ components: {
15
+ BaseTextarea,
16
+ },
17
+ setup() {
18
+ const value = ref('');
19
+ return { args, value };
20
+ },
21
+ template: `
22
+ <form @submit.prevent="" class="border-none">
23
+ <BaseTextarea v-model="value" v-bind="args" class="w-full"></BaseTextarea>
24
+ </form>
25
+ `,
26
+ });
27
+
28
+ export const Demo = Template.bind({});
29
+ Demo.args = {};
30
+
31
+ export const Disabled = Template.bind({});
32
+ Disabled.args = {
33
+ modelValue: 'Lorem ipsum...',
34
+ disabled: true,
35
+ };
@@ -7,7 +7,7 @@
7
7
  :disabled="disabled"
8
8
  :required="required"
9
9
  :rows="rows"
10
- class="mb-0 block rounded"
10
+ class="mb-0 block rounded border-slate-300 disabled:cursor-not-allowed disabled:text-slate-300"
11
11
  @input="$emit('update:modelValue', transformInputValue($event))"
12
12
  />
13
13
  </template>
@@ -0,0 +1,49 @@
1
+ import BaseTextareaAutoresize from './BaseTextareaAutoresize.vue';
2
+
3
+ export default {
4
+ title: 'Form/BaseTextareaAutoresize',
5
+ component: BaseTextareaAutoresize,
6
+ args: {
7
+ name: 'bio',
8
+ placeholder: 'Describe your complete life in 4 sentences...',
9
+ },
10
+ };
11
+
12
+ const Template = (args) => ({
13
+ components: {
14
+ BaseTextareaAutoresize,
15
+ },
16
+ setup() {
17
+ function onSubmit() {
18
+ alert('submit');
19
+ }
20
+ const value = ref('');
21
+ return { args, value, onSubmit };
22
+ },
23
+ template: `
24
+ <form @submit.prevent="onSubmit" class="border-none">
25
+ <BaseTextareaAutoresize
26
+ v-model="value"
27
+ v-bind="args"
28
+ class="w-full"
29
+ @submit="onSubmit"
30
+ ></BaseTextareaAutoresize>
31
+ </form>
32
+
33
+ <pre class="mt-4 bg-slate-800 font-light text-xs p-3 rounded whitespace-pre-wrap text-white">{{ {value} }}</pre>
34
+ `,
35
+ });
36
+
37
+ export const Demo = Template.bind({});
38
+ Demo.args = {};
39
+
40
+ export const SubmitOnEnter = Template.bind({});
41
+ SubmitOnEnter.args = {
42
+ submitOnEnter: true,
43
+ };
44
+
45
+ export const Disabled = Template.bind({});
46
+ Disabled.args = {
47
+ modelValue: 'Lorem ipsum...',
48
+ disabled: true,
49
+ };
@@ -1,117 +1,113 @@
1
1
  <template>
2
- <div ref="wrapper" class="wrapper">
2
+ <div
3
+ ref="wrapper"
4
+ class="wrapper grid"
5
+ :style="{ maxHeight: maxHeight + 'px' }"
6
+ >
3
7
  <textarea
4
8
  :value="modelValue"
5
9
  :name="name"
6
10
  :placeholder="placeholder"
11
+ :disabled="disabled"
12
+ class="resize-none"
13
+ :class="BASE_TEXTAREA_CLASSES"
14
+ :style="{ maxHeight: maxHeight + 'px', gridArea: BASE_GRID_AREA }"
7
15
  rows="1"
8
- class="focus:outline-none focus:ring-0"
9
16
  @input="onInput"
10
17
  @keyup="onKeyUp"
11
18
  @keydown="onKeyDown"
12
19
  @focus="onFocus"
13
20
  />
21
+ <div
22
+ class="invisible whitespace-pre-wrap"
23
+ :class="BASE_TEXTAREA_CLASSES"
24
+ :style="{
25
+ content: DIV_CONTENT,
26
+ maxHeight: maxHeight + 'px',
27
+ gridArea: BASE_GRID_AREA,
28
+ }"
29
+ >
30
+ {{ modelValue }} {{ ' ' }}
31
+ </div>
14
32
  </div>
15
33
  </template>
16
34
 
17
- <script lang="ts">
18
- /* eslint-disable @typescript-eslint/no-explicit-any */
19
- import { defineComponent, inject } from 'vue';
35
+ <script lang="ts" setup>
36
+ import { Ref } from 'vue';
20
37
 
21
- const keys = {} as { [key: string]: boolean };
38
+ const BASE_TEXTAREA_CLASSES =
39
+ 'py-2 px-3 font-normal text-base disabled:cursor-not-allowed disabled:text-slate-300 font-sans rounded leading-normal tracking-normal border border-slate-300';
40
+
41
+ const BASE_GRID_AREA = '1 / 1 / 2 / 2';
22
42
 
23
- export default defineComponent({
24
- props: {
25
- modelValue: {
26
- default: '',
27
- type: String,
28
- },
29
- placeholder: {
30
- default: '',
31
- type: String,
32
- },
33
- name: {
34
- required: true,
35
- type: String,
36
- },
43
+ /* Note the weird space! Needed to prevent jumpy behavior */
44
+ const DIV_CONTENT = "attr(data-replicated-value) ' '";
45
+
46
+ const props = defineProps({
47
+ modelValue: {
48
+ default: '',
49
+ type: String,
37
50
  },
38
- emits: ['update:modelValue', 'submit', 'focus', 'input'],
39
- setup() {
40
- const mobile = inject('mobile') as boolean;
41
- return {
42
- mobile,
43
- };
51
+ placeholder: {
52
+ default: '',
53
+ type: String,
44
54
  },
45
- watch: {
46
- modelValue(value: string) {
47
- this.updateReplicatedValue(value);
48
- },
55
+ name: {
56
+ required: true,
57
+ type: String,
49
58
  },
50
- methods: {
51
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
- onInput(e: any) {
53
- const value = e.target.value ?? '';
54
- this.$emit('update:modelValue', value);
55
- this.$emit('input', e);
56
- this.updateReplicatedValue(value);
57
- },
58
- updateReplicatedValue(value: string) {
59
- const wrapper = this.$refs.wrapper as HTMLElement;
60
- wrapper.dataset.replicatedValue = value;
61
- },
62
- onKeyDown(event: any) {
63
- keys[event.key as number] = true;
64
-
65
- if (keys['Enter'] && !keys['Shift'] && !this.mobile) {
66
- this.$emit('submit');
67
- event.preventDefault();
68
- }
69
- },
70
- onKeyUp(event: any) {
71
- delete keys[event.key];
72
- },
73
- onFocus(event: any) {
74
- this.$emit('focus', event);
75
- },
59
+ maxHeight: {
60
+ default: 100,
61
+ type: Number,
62
+ },
63
+ /**
64
+ * Setting this to true will trigger the 'submit' event while pressing Enter.
65
+ * Users will be able to add a line break while pressing Shift + Enter.
66
+ */
67
+ submitOnEnter: {
68
+ default: false,
69
+ type: Boolean,
70
+ },
71
+ disabled: {
72
+ default: false,
73
+ type: Boolean,
76
74
  },
77
75
  });
78
- </script>
79
76
 
80
- <style lang="postcss" scoped>
81
- .wrapper {
82
- /* easy way to plop the elements on top of each other and have them both sized based on the tallest one's height */
83
- display: grid;
84
- }
85
- .wrapper::after {
86
- /* Note the weird space! Needed to prevent jumpy behavior */
87
- content: attr(data-replicated-value) ' ';
77
+ const emit = defineEmits(['update:modelValue', 'submit', 'focus', 'input']);
88
78
 
89
- /* This is how textarea text behaves */
90
- white-space: pre-wrap;
79
+ const wrapper = ref(null) as Ref<null | HTMLDivElement>;
91
80
 
92
- /* Hidden from view, clicks, and screen readers */
93
- visibility: hidden;
81
+ const keys = {} as { [key: string]: boolean };
82
+
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ function onInput(e: any) {
85
+ const value = e.target.value ?? '';
86
+ emit('update:modelValue', value);
87
+ emit('input', e);
94
88
  }
95
- .wrapper > textarea {
96
- /* You could leave this, but after a user resizes, then it ruins the auto sizing */
97
- resize: none;
98
89
 
99
- @apply focus:outline-none;
100
- @apply focus-visible:outline-none;
90
+ function onKeyDown(event: KeyboardEvent) {
91
+ keys[event.key] = true;
92
+
93
+ if (keys['Enter'] && !keys['Shift'] && props.submitOnEnter) {
94
+ // If submit triggers alert, Enter wont be delete from keys,
95
+ // manually remove it here
96
+ delete keys['Enter'];
97
+
98
+ // Submit event to listen to
99
+ emit('submit');
100
+
101
+ // Prevent adding an EOL the the textarea
102
+ event.preventDefault();
103
+ }
101
104
  }
102
- .wrapper > textarea,
103
- .wrapper::after {
104
- /* Identical styling required!! */
105
- padding: 0.5rem;
106
- font: inherit;
107
105
 
108
- @apply text-base;
109
- @apply max-h-28;
110
- @apply border-transparent;
111
- @apply bg-slate-100;
112
- @apply rounded-lg;
106
+ function onKeyUp(event: KeyboardEvent) {
107
+ delete keys[event.key];
108
+ }
113
109
 
114
- /* Place on top of each other */
115
- grid-area: 1 / 1 / 2 / 2;
110
+ function onFocus(event: FocusEvent) {
111
+ emit('focus', event);
116
112
  }
117
- </style>
113
+ </script>
@@ -0,0 +1,135 @@
1
+ import BaseHasMany from './BaseHasMany.vue';
2
+
3
+ export default {
4
+ title: 'Form/BaseHasMany',
5
+ component: BaseHasMany,
6
+ argTypes: {},
7
+ args: {
8
+ url: 'https://effettandem.com/api/content/articles',
9
+ field: 'title',
10
+ foreignKey: 'id',
11
+ },
12
+ decorators: [() => ({ template: '<div class="mb-36"><story/></div>' })],
13
+ };
14
+
15
+ const Template = (args) => {
16
+ return {
17
+ components: { BaseHasMany },
18
+ setup() {
19
+ const value = ref([]);
20
+ return { args, value };
21
+ },
22
+ template: `
23
+ <BaseHasMany
24
+ v-model="value"
25
+ v-bind="args"
26
+ ></BaseHasMany>
27
+ <p class="mt-5 text-sm">Value: <span class="bg-slate-200 font-mono px-1 py-px rounded">{{ value ?? '[]' }}</span></p>
28
+ `,
29
+ };
30
+ };
31
+
32
+ export const Demo = Template.bind({});
33
+ Demo.args = {};
34
+
35
+ export const Disabled = (args) => {
36
+ return {
37
+ components: { BaseHasMany },
38
+ setup() {
39
+ const value = ref([]);
40
+ return { args, value };
41
+ },
42
+ template: `<BaseHasMany
43
+ v-bind="args"
44
+ v-model="value"
45
+ :current-models="[{title: 'Dark Vader', id: 1}]"
46
+ :disabled="true"
47
+ ></BaseHasMany>`,
48
+ };
49
+ };
50
+
51
+ export const Maximum = Template.bind({});
52
+ Maximum.args = {
53
+ max: 3,
54
+ };
55
+
56
+ export const SlotOption = (args) => {
57
+ return {
58
+ components: { BaseHasMany },
59
+ setup() {
60
+ const value = ref([]);
61
+ return { args, value };
62
+ },
63
+ template: `
64
+ <div class="mb-20">
65
+ <BaseHasMany
66
+ v-model="value"
67
+ v-bind="args"
68
+ >
69
+ <template #option="{ option, active, selected }">
70
+ <div
71
+ class="rounded px-2 py-1"
72
+ :class="{
73
+ 'hover:bg-slate-100': !active && !selected,
74
+ 'bg-slate-200 hover:bg-slate-300': active && !selected,
75
+ 'bg-blue-500 text-white hover:bg-blue-600': !active && selected,
76
+ 'bg-blue-600 text-white hover:bg-blue-700': active && selected,
77
+ }"
78
+ >
79
+ <p class="text-sm font-medium">{{ option.title }}</p>
80
+ <p class="opacity-60 text-xs">{{ option.owner?.name }}</p>
81
+ </div>
82
+ </template>
83
+ </BaseHasMany>
84
+ </div>
85
+ `,
86
+ };
87
+ };
88
+
89
+ export const SlotFooter = (args) => {
90
+ return {
91
+ components: { BaseHasMany },
92
+ setup() {
93
+ const value = ref([]);
94
+ function onClick() {
95
+ alert(1);
96
+ }
97
+ return { args, value, onClick };
98
+ },
99
+ template: `
100
+ <BaseHasMany
101
+ v-model="value"
102
+ v-bind="args"
103
+ >
104
+ <template #footer>
105
+ <div class="text-center p-2 border-t">
106
+ <button @click=onClick class="btn btn-sm w-full btn-slate-200-outline">This is the footer 💯</button>
107
+ </div>
108
+ </template>
109
+ </BaseHasMany>
110
+ `,
111
+ };
112
+ };
113
+
114
+ export const SlotEmpty = (args) => {
115
+ return {
116
+ components: { BaseHasMany },
117
+ setup() {
118
+ const value = ref([]);
119
+ return { args, value };
120
+ },
121
+ template: `
122
+ <BaseHasMany
123
+ v-model="value"
124
+ v-bind="args"
125
+ >
126
+ <template #empty="props">
127
+ <div>
128
+ <div v-if="props.firstSearch" class="text-center py-10 p-6">🤓🤓🤓</div>
129
+ <div v-else class="text-center px-6 py-20">Start your search... 🔎</div>
130
+ </div>
131
+ </template>
132
+ </BaseHasMany>
133
+ `,
134
+ };
135
+ };