sprintify-ui 0.0.32 → 0.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/sprintify-ui.es.js +3616 -3402
- package/dist/types/src/components/BaseAutocomplete.vue.d.ts +9 -9
- package/dist/types/src/components/BaseAutocompleteFetch.vue.d.ts +9 -9
- package/dist/types/src/components/BaseBelongsTo.vue.d.ts +10 -10
- package/dist/types/src/components/BaseButtonGroup.vue.d.ts +23 -23
- package/dist/types/src/components/BaseFilePicker.vue.d.ts +1 -1
- package/dist/types/src/components/BaseFileUploader.vue.d.ts +4 -4
- package/dist/types/src/components/BaseHasMany.vue.d.ts +0 -1
- package/dist/types/src/components/BaseRadioGroup.vue.d.ts +226 -0
- package/dist/types/src/components/BaseTagAutocomplete.vue.d.ts +0 -1
- package/dist/types/src/components/BaseTagAutocompleteFetch.vue.d.ts +0 -1
- package/dist/types/src/components/index.d.ts +3 -1
- package/dist/types/src/composables/hasOptions.d.ts +7 -0
- package/dist/types/src/types/types.d.ts +0 -2
- package/package.json +1 -1
- package/src/components/BaseAutocomplete.vue +29 -40
- package/src/components/BaseAutocompleteFetch.vue +2 -2
- package/src/components/BaseBelongsTo.vue +3 -3
- package/src/components/BaseButtonGroup.stories.js +5 -8
- package/src/components/BaseButtonGroup.vue +24 -66
- package/src/components/BaseRadioGroup.stories.js +88 -0
- package/src/components/BaseRadioGroup.vue +84 -0
- package/src/components/BaseTagAutocomplete.stories.js +4 -6
- package/src/components/BaseTagAutocomplete.vue +35 -53
- package/src/components/index.ts +4 -0
- package/src/composables/hasOptions.ts +68 -0
- package/src/types/types.ts +0 -4
|
@@ -11,6 +11,7 @@ import BaseBelongsTo from './BaseBelongsTo.vue';
|
|
|
11
11
|
import BaseBoolean from './BaseBoolean.vue';
|
|
12
12
|
import BaseBreadcrumbs from './BaseBreadcrumbs.vue';
|
|
13
13
|
import BaseButton from './BaseButton.vue';
|
|
14
|
+
import BaseButtonGroup from './BaseButtonGroup.vue';
|
|
14
15
|
import BaseCard from './BaseCard.vue';
|
|
15
16
|
import BaseCardRow from './BaseCardRow.vue';
|
|
16
17
|
import BaseCharacterCounter from './BaseCharacterCounter.vue';
|
|
@@ -45,6 +46,7 @@ import BasePagination from './BasePagination.vue';
|
|
|
45
46
|
import BasePanel from './BasePanel.vue';
|
|
46
47
|
import BasePassword from './BasePassword.vue';
|
|
47
48
|
import BaseProgressCircle from './BaseProgressCircle.vue';
|
|
49
|
+
import BaseRadioGroup from './BaseRadioGroup.vue';
|
|
48
50
|
import BaseReadMore from './BaseReadMore.vue';
|
|
49
51
|
import BaseSelect from './BaseSelect.vue';
|
|
50
52
|
import BaseSideNavigation from './BaseSideNavigation.vue';
|
|
@@ -64,4 +66,4 @@ import BaseLayoutStacked from './BaseLayoutStacked.vue';
|
|
|
64
66
|
import BaseLayoutStackedConfigurable from './BaseLayoutStackedConfigurable.vue';
|
|
65
67
|
import BaseLayoutSidebar from './BaseLayoutSidebar.vue';
|
|
66
68
|
import BaseLayoutSidebarConfigurable from './BaseLayoutSidebarConfigurable.vue';
|
|
67
|
-
export { BaseActionItem, BaseAlert, BaseApp, BaseAppDialogs, BaseAppNotifications, BaseAutocomplete, BaseAutocompleteFetch, BaseAvatar, BaseBadge, BaseBelongsTo, BaseBoolean, BaseBreadcrumbs, BaseButton, BaseCard, BaseCardRow, BaseCharacterCounter, BaseClipboard, BaseContainer, BaseCounter, BaseDataIterator, BaseDataTable, BaseDatePicker, BaseDateSelect, BaseDescriptionList, BaseDescriptionListItem, BaseDialog, BaseFilePicker, BaseFileUploader, BaseHasMany, BaseIcon, BaseInput, BaseInputLabel, BaseLoadingCover, BaseMediaItem, BaseMediaLibrary, BaseMediaPreview, BaseMenu, BaseMenuItem, BaseModalCenter, BaseModalSide, BaseNavbar, BaseNavbarItem, BaseNavbarItemContent, BasePagination, BasePanel, BasePassword, BaseProgressCircle, BaseReadMore, BaseSelect, BaseSideNavigation, BaseSideNavigationItem, BaseSkeleton, BaseSwitch, BaseSystemAlert, BaseTabs, BaseTabItem, BaseTagAutocomplete, BaseTagAutocompleteFetch, BaseTable, BaseTableColumn, BaseTextarea, BaseTextareaAutoresize, BaseLayoutStacked, BaseLayoutStackedConfigurable, BaseLayoutSidebar, BaseLayoutSidebarConfigurable, };
|
|
69
|
+
export { BaseActionItem, BaseAlert, BaseApp, BaseAppDialogs, BaseAppNotifications, BaseAutocomplete, BaseAutocompleteFetch, BaseAvatar, BaseBadge, BaseBelongsTo, BaseBoolean, BaseBreadcrumbs, BaseButton, BaseButtonGroup, BaseCard, BaseCardRow, BaseCharacterCounter, BaseClipboard, BaseContainer, BaseCounter, BaseDataIterator, BaseDataTable, BaseDatePicker, BaseDateSelect, BaseDescriptionList, BaseDescriptionListItem, BaseDialog, BaseFilePicker, BaseFileUploader, BaseHasMany, BaseIcon, BaseInput, BaseInputLabel, BaseLoadingCover, BaseMediaItem, BaseMediaLibrary, BaseMediaPreview, BaseMenu, BaseMenuItem, BaseModalCenter, BaseModalSide, BaseNavbar, BaseNavbarItem, BaseNavbarItemContent, BasePagination, BasePanel, BasePassword, BaseProgressCircle, BaseRadioGroup, BaseReadMore, BaseSelect, BaseSideNavigation, BaseSideNavigationItem, BaseSkeleton, BaseSwitch, BaseSystemAlert, BaseTabs, BaseTabItem, BaseTagAutocomplete, BaseTagAutocompleteFetch, BaseTable, BaseTableColumn, BaseTextarea, BaseTextareaAutoresize, BaseLayoutStacked, BaseLayoutStackedConfigurable, BaseLayoutSidebar, BaseLayoutSidebarConfigurable, };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
import { NormalizedOption, Option } from '@/types/types';
|
|
3
|
+
export declare function useHasOptions(modelValue: Ref<Option[] | Option | null | undefined>, options: Ref<Option[]>, labelKey: Ref<string>, valueKey: Ref<string>, multiple?: Ref<boolean>): {
|
|
4
|
+
normalizedOptions: import("vue").ComputedRef<NormalizedOption[]>;
|
|
5
|
+
normalizedModelValue: import("vue").ComputedRef<NormalizedOption | NormalizedOption[] | null>;
|
|
6
|
+
isSelected: (option: NormalizedOption) => boolean;
|
|
7
|
+
};
|
|
@@ -14,13 +14,11 @@ export interface DataTableQuery extends Record<string, any> {
|
|
|
14
14
|
}
|
|
15
15
|
export type OptionValue = string | number;
|
|
16
16
|
export type Option = Record<string, any>;
|
|
17
|
-
export type Selection = Option | null | undefined;
|
|
18
17
|
export type NormalizedOption = {
|
|
19
18
|
option: Option;
|
|
20
19
|
value: OptionValue;
|
|
21
20
|
label: string;
|
|
22
21
|
};
|
|
23
|
-
export type NormalizedSelection = NormalizedOption | null | undefined;
|
|
24
22
|
export type MediaLibraryPayload = {
|
|
25
23
|
to_remove: string[];
|
|
26
24
|
to_add: UploadedFile[];
|
package/package.json
CHANGED
|
@@ -78,10 +78,7 @@
|
|
|
78
78
|
<slot
|
|
79
79
|
name="option"
|
|
80
80
|
:option="option.option"
|
|
81
|
-
:selected="
|
|
82
|
-
normalizedModelValue &&
|
|
83
|
-
normalizedModelValue.value == option.value
|
|
84
|
-
"
|
|
81
|
+
:selected="isSelected(option)"
|
|
85
82
|
:active="optionActive && optionActive.value == option.value"
|
|
86
83
|
>
|
|
87
84
|
<div
|
|
@@ -125,20 +122,16 @@
|
|
|
125
122
|
|
|
126
123
|
<script lang="ts" setup>
|
|
127
124
|
import { get } from 'lodash';
|
|
128
|
-
import { PropType, Ref } from 'vue';
|
|
129
|
-
import {
|
|
130
|
-
NormalizedOption,
|
|
131
|
-
Option,
|
|
132
|
-
Selection,
|
|
133
|
-
NormalizedSelection,
|
|
134
|
-
} from '@/types/types';
|
|
125
|
+
import { PropType, Ref, ComputedRef } from 'vue';
|
|
126
|
+
import { NormalizedOption, Option } from '@/types/types';
|
|
135
127
|
import { useInfiniteScroll, useMutationObserver } from '@vueuse/core';
|
|
136
128
|
import BaseSkeleton from './BaseSkeleton.vue';
|
|
129
|
+
import { useHasOptions } from '@/composables/hasOptions';
|
|
137
130
|
|
|
138
131
|
const props = defineProps({
|
|
139
132
|
modelValue: {
|
|
140
133
|
default: undefined,
|
|
141
|
-
type: [Object, null] as PropType<
|
|
134
|
+
type: [Object, null] as PropType<Option | null | undefined>,
|
|
142
135
|
},
|
|
143
136
|
options: {
|
|
144
137
|
required: true,
|
|
@@ -193,6 +186,19 @@ const selectionIndex = ref(0);
|
|
|
193
186
|
const inputElement = ref(null) as Ref<HTMLInputElement | null>;
|
|
194
187
|
const dropdown = ref(null) as Ref<HTMLElement | null>;
|
|
195
188
|
|
|
189
|
+
const hasOptions = useHasOptions(
|
|
190
|
+
computed(() => props.modelValue),
|
|
191
|
+
computed(() => props.options),
|
|
192
|
+
computed(() => props.labelKey),
|
|
193
|
+
computed(() => props.valueKey),
|
|
194
|
+
computed(() => false)
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const isSelected = hasOptions.isSelected;
|
|
198
|
+
const normalizedOptions = hasOptions.normalizedOptions;
|
|
199
|
+
const normalizedModelValue =
|
|
200
|
+
hasOptions.normalizedModelValue as ComputedRef<NormalizedOption | null>;
|
|
201
|
+
|
|
196
202
|
onMounted(() => {
|
|
197
203
|
useInfiniteScroll(
|
|
198
204
|
dropdown.value,
|
|
@@ -211,17 +217,7 @@ const optionActive = computed(() => {
|
|
|
211
217
|
);
|
|
212
218
|
});
|
|
213
219
|
|
|
214
|
-
|
|
215
|
-
if (!props.modelValue) {
|
|
216
|
-
return null;
|
|
217
|
-
}
|
|
218
|
-
return {
|
|
219
|
-
label: props.modelValue[props.labelKey] as string,
|
|
220
|
-
value: props.modelValue[props.valueKey] as string | number,
|
|
221
|
-
option: props.modelValue,
|
|
222
|
-
};
|
|
223
|
-
});
|
|
224
|
-
|
|
220
|
+
// Update the keywords when the model value changes
|
|
225
221
|
watch(
|
|
226
222
|
() => normalizedModelValue.value,
|
|
227
223
|
() => {
|
|
@@ -234,16 +230,6 @@ watch(
|
|
|
234
230
|
{ immediate: true }
|
|
235
231
|
);
|
|
236
232
|
|
|
237
|
-
const normalizedOptions = computed(() => {
|
|
238
|
-
return props.options.map((option) => {
|
|
239
|
-
return {
|
|
240
|
-
label: option[props.labelKey] as string,
|
|
241
|
-
value: option[props.valueKey] as string | number,
|
|
242
|
-
option: option,
|
|
243
|
-
} as NormalizedOption;
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
|
|
247
233
|
const filteredNormalizedOptions = computed((): NormalizedOption[] => {
|
|
248
234
|
return normalizedOptions.value.filter((option) => {
|
|
249
235
|
if (props.filter !== undefined) {
|
|
@@ -276,6 +262,8 @@ const onTextFocus = () => {
|
|
|
276
262
|
emit('focus');
|
|
277
263
|
};
|
|
278
264
|
|
|
265
|
+
// If keywords is changed but no new selection was made,
|
|
266
|
+
// update keywords to original value
|
|
279
267
|
const onTextBlur = () => {
|
|
280
268
|
timerId.value = setTimeout(() => {
|
|
281
269
|
showDropdown.value = false;
|
|
@@ -305,7 +293,7 @@ const onTextKeydown = (event: KeyboardEvent) => {
|
|
|
305
293
|
}
|
|
306
294
|
|
|
307
295
|
if (key === 'ArrowDown') {
|
|
308
|
-
if (selectionIndex.value <
|
|
296
|
+
if (selectionIndex.value < filteredNormalizedOptions.value.length - 1) {
|
|
309
297
|
selectionIndex.value++;
|
|
310
298
|
} else {
|
|
311
299
|
selectionIndex.value = 0;
|
|
@@ -317,7 +305,10 @@ const onTextKeydown = (event: KeyboardEvent) => {
|
|
|
317
305
|
if (selectionIndex.value > 0) {
|
|
318
306
|
selectionIndex.value--;
|
|
319
307
|
} else {
|
|
320
|
-
selectionIndex.value = Math.max(
|
|
308
|
+
selectionIndex.value = Math.max(
|
|
309
|
+
0,
|
|
310
|
+
filteredNormalizedOptions.value.length - 1
|
|
311
|
+
);
|
|
321
312
|
}
|
|
322
313
|
return;
|
|
323
314
|
}
|
|
@@ -333,9 +324,7 @@ const onTextKeydown = (event: KeyboardEvent) => {
|
|
|
333
324
|
|
|
334
325
|
const optionClass = (option: NormalizedOption) => {
|
|
335
326
|
const active = optionActive.value && optionActive.value.value == option.value;
|
|
336
|
-
const selected =
|
|
337
|
-
normalizedModelValue.value &&
|
|
338
|
-
normalizedModelValue.value.value == option.value;
|
|
327
|
+
const selected = isSelected(option);
|
|
339
328
|
|
|
340
329
|
if (selected) {
|
|
341
330
|
if (active) {
|
|
@@ -359,12 +348,12 @@ const clear = () => {
|
|
|
359
348
|
inputElement.value?.focus();
|
|
360
349
|
};
|
|
361
350
|
|
|
362
|
-
const onSelect = (normalizedModelValue:
|
|
351
|
+
const onSelect = (normalizedModelValue: Option | null | undefined) => {
|
|
363
352
|
update(normalizedModelValue);
|
|
364
353
|
inputElement.value?.blur();
|
|
365
354
|
};
|
|
366
355
|
|
|
367
|
-
const update = (normalizedSelection:
|
|
356
|
+
const update = (normalizedSelection: Option | null | undefined) => {
|
|
368
357
|
const selection = normalizedSelection ? normalizedSelection.option : null;
|
|
369
358
|
if (normalizedSelection) {
|
|
370
359
|
setKeywordsWithoutEvent(normalizedSelection.label);
|
|
@@ -61,14 +61,14 @@
|
|
|
61
61
|
import { config } from '../';
|
|
62
62
|
import { debounce } from 'lodash';
|
|
63
63
|
import { PropType, Ref } from 'vue';
|
|
64
|
-
import { Option
|
|
64
|
+
import { Option } from '@/types/types';
|
|
65
65
|
import { RouteLocationRaw } from 'vue-router';
|
|
66
66
|
import BaseAutocomplete from './BaseAutocomplete.vue';
|
|
67
67
|
|
|
68
68
|
const props = defineProps({
|
|
69
69
|
modelValue: {
|
|
70
70
|
default: undefined,
|
|
71
|
-
type: [Object, null] as PropType<
|
|
71
|
+
type: [Object, null] as PropType<Option | null | undefined>,
|
|
72
72
|
},
|
|
73
73
|
url: {
|
|
74
74
|
required: true,
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
</template>
|
|
25
25
|
|
|
26
26
|
<script lang="ts" setup>
|
|
27
|
-
import { Selection } from '@/types/types';
|
|
28
27
|
import { PropType } from 'vue';
|
|
29
28
|
import { RouteLocationRaw } from 'vue-router';
|
|
30
29
|
import { AxiosResponse } from 'axios';
|
|
31
30
|
import { config } from '@/index';
|
|
32
31
|
import BaseAutocompleteFetch from './BaseAutocompleteFetch.vue';
|
|
32
|
+
import { Option } from '@/types/types';
|
|
33
33
|
|
|
34
34
|
const props = defineProps({
|
|
35
35
|
modelValue: {
|
|
@@ -72,7 +72,7 @@ const props = defineProps({
|
|
|
72
72
|
},
|
|
73
73
|
currentModel: {
|
|
74
74
|
default: null,
|
|
75
|
-
type: [Object, null] as PropType<
|
|
75
|
+
type: [Object, null] as PropType<Option | null>,
|
|
76
76
|
},
|
|
77
77
|
createNewUrl: {
|
|
78
78
|
default: '',
|
|
@@ -120,7 +120,7 @@ watch(
|
|
|
120
120
|
{ immediate: true }
|
|
121
121
|
);
|
|
122
122
|
|
|
123
|
-
function onUpdate(newModel:
|
|
123
|
+
function onUpdate(newModel: Option | null) {
|
|
124
124
|
if (!newModel) {
|
|
125
125
|
model.value = null;
|
|
126
126
|
emit('update:modelValue', null);
|
|
@@ -70,17 +70,14 @@ export const SlotOption = (args) => ({
|
|
|
70
70
|
v-bind="args"
|
|
71
71
|
v-model="value"
|
|
72
72
|
:options="options"
|
|
73
|
+
button-class="btn btn-xs"
|
|
74
|
+
button-selected-class="btn-black"
|
|
73
75
|
>
|
|
74
|
-
<template #option="{ option
|
|
75
|
-
<
|
|
76
|
-
class="btn btn-xs flex items-center space-x-1 font-semibold"
|
|
77
|
-
:class="[isActive(option) ? 'btn-black' : '']"
|
|
78
|
-
type="button"
|
|
79
|
-
@click="onSelect(option)"
|
|
80
|
-
>
|
|
76
|
+
<template #option="{ option }">
|
|
77
|
+
<div class="flex items-center space-x-1 font-semibold">
|
|
81
78
|
<div class="w-3 h-3 rounded" :style="{ backgroundColor: option.value }"></div>
|
|
82
79
|
<div>{{ option.label }}</div>
|
|
83
|
-
</
|
|
80
|
+
</div>
|
|
84
81
|
</template>
|
|
85
82
|
</BaseButtonGroup>
|
|
86
83
|
`,
|
|
@@ -5,24 +5,24 @@
|
|
|
5
5
|
:key="option.value"
|
|
6
6
|
:style="{ padding: spacing }"
|
|
7
7
|
>
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
:
|
|
11
|
-
:
|
|
12
|
-
|
|
8
|
+
<button
|
|
9
|
+
:type="buttonType"
|
|
10
|
+
:disabled="disabled"
|
|
11
|
+
:class="[
|
|
12
|
+
buttonClass,
|
|
13
|
+
isSelected(option) ? buttonSelectedClass : buttonUnselectedClass,
|
|
14
|
+
]"
|
|
15
|
+
@click="onSelect(option)"
|
|
13
16
|
>
|
|
14
|
-
<
|
|
15
|
-
|
|
17
|
+
<slot
|
|
18
|
+
name="option"
|
|
19
|
+
:selected="isSelected(option)"
|
|
20
|
+
:option="option"
|
|
16
21
|
:disabled="disabled"
|
|
17
|
-
:class="[
|
|
18
|
-
buttonClass,
|
|
19
|
-
isActive(option) ? buttonActiveClass : buttonInactiveClass,
|
|
20
|
-
]"
|
|
21
|
-
@click="onSelect(option)"
|
|
22
22
|
>
|
|
23
23
|
{{ option.label }}
|
|
24
|
-
</
|
|
25
|
-
</
|
|
24
|
+
</slot>
|
|
25
|
+
</button>
|
|
26
26
|
</div>
|
|
27
27
|
</div>
|
|
28
28
|
</template>
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
<script lang="ts" setup>
|
|
31
31
|
import { PropType } from 'vue';
|
|
32
32
|
import { NormalizedOption, Option } from '@/types/types';
|
|
33
|
-
import { cloneDeep, isArray
|
|
33
|
+
import { cloneDeep, isArray } from 'lodash';
|
|
34
|
+
import { useHasOptions } from '@/composables/hasOptions';
|
|
34
35
|
|
|
35
36
|
const props = defineProps({
|
|
36
37
|
modelValue: {
|
|
@@ -55,11 +56,11 @@ const props = defineProps({
|
|
|
55
56
|
default: 'btn btn-sm',
|
|
56
57
|
type: String,
|
|
57
58
|
},
|
|
58
|
-
|
|
59
|
+
buttonSelectedClass: {
|
|
59
60
|
default: 'btn-primary',
|
|
60
61
|
type: String,
|
|
61
62
|
},
|
|
62
|
-
|
|
63
|
+
buttonUnselectedClass: {
|
|
63
64
|
default: '',
|
|
64
65
|
type: String,
|
|
65
66
|
},
|
|
@@ -87,57 +88,14 @@ const props = defineProps({
|
|
|
87
88
|
|
|
88
89
|
const emit = defineEmits(['update:modelValue']);
|
|
89
90
|
|
|
90
|
-
const normalizedModelValue =
|
|
91
|
-
()
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return props.modelValue.map((option) => {
|
|
97
|
-
return {
|
|
98
|
-
label: option[props.labelKey] as string,
|
|
99
|
-
value: option[props.valueKey] as string | number,
|
|
100
|
-
option: option,
|
|
101
|
-
} as NormalizedOption;
|
|
102
|
-
});
|
|
103
|
-
} else {
|
|
104
|
-
if (!isObject(props.modelValue)) {
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
label: props.modelValue[props.labelKey as never] as string,
|
|
110
|
-
value: props.modelValue[props.valueKey as never] as string | number,
|
|
111
|
-
option: props.modelValue,
|
|
112
|
-
} as NormalizedOption;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
91
|
+
const { normalizedOptions, normalizedModelValue, isSelected } = useHasOptions(
|
|
92
|
+
computed(() => props.modelValue),
|
|
93
|
+
computed(() => props.options),
|
|
94
|
+
computed(() => props.labelKey),
|
|
95
|
+
computed(() => props.valueKey),
|
|
96
|
+
computed(() => props.multiple)
|
|
115
97
|
);
|
|
116
98
|
|
|
117
|
-
const normalizedOptions = computed((): NormalizedOption[] => {
|
|
118
|
-
return props.options.map((option) => {
|
|
119
|
-
return {
|
|
120
|
-
label: option[props.labelKey] as string,
|
|
121
|
-
value: option[props.valueKey] as string | number,
|
|
122
|
-
option: option,
|
|
123
|
-
} as NormalizedOption;
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
function isActive(option: NormalizedOption): boolean {
|
|
128
|
-
if (isArray(normalizedModelValue.value)) {
|
|
129
|
-
return normalizedModelValue.value.some((modelValue) => {
|
|
130
|
-
return modelValue.value === option.value;
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (isObject(normalizedModelValue.value)) {
|
|
135
|
-
return normalizedModelValue.value.value == option.value;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
99
|
function onSelect(option: NormalizedOption) {
|
|
142
100
|
if (props.multiple) {
|
|
143
101
|
let newModalValue = [] as NormalizedOption[];
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import BaseRadioGroup from './BaseRadioGroup.vue';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Form/BaseRadioGroup',
|
|
5
|
+
component: BaseRadioGroup,
|
|
6
|
+
argTypes: {},
|
|
7
|
+
args: {
|
|
8
|
+
name: 'character',
|
|
9
|
+
labelKey: 'label',
|
|
10
|
+
valueKey: 'value',
|
|
11
|
+
options: [
|
|
12
|
+
{ label: 'Dark Vader', value: 'dark_vader' },
|
|
13
|
+
{ label: 'Darth Maul', value: 'darth_maul' },
|
|
14
|
+
{ label: 'Dark Sidious', value: 'dark_sidious' },
|
|
15
|
+
{ label: 'Obi Wan Kenobi', value: 'obiwan' },
|
|
16
|
+
{ label: 'Anakin Skywalker', value: 'anakin' },
|
|
17
|
+
{ label: 'Mace Windu', value: 'windu' },
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
decorators: [() => ({ template: '<div class="mb-36"><story/></div>' })],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const Template = (args) => ({
|
|
24
|
+
components: { BaseRadioGroup },
|
|
25
|
+
setup() {
|
|
26
|
+
const value = ref(null);
|
|
27
|
+
function onSubmit() {
|
|
28
|
+
alert('submit');
|
|
29
|
+
}
|
|
30
|
+
return { args, value, onSubmit };
|
|
31
|
+
},
|
|
32
|
+
template: `
|
|
33
|
+
<form @submit.prevent="onSubmit">
|
|
34
|
+
<BaseRadioGroup v-model="value" v-bind="args"></BaseRadioGroup>
|
|
35
|
+
<button type="submit" class="btn btn-primary mt-4">Submit</button>
|
|
36
|
+
<p class="mt-5 text-sm">Value: <span class="bg-slate-200 font-mono px-1 py-px rounded">{{ value ?? 'NULL' }}</span></p>
|
|
37
|
+
</form>
|
|
38
|
+
`,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const Demo = Template.bind({});
|
|
42
|
+
Demo.args = {};
|
|
43
|
+
|
|
44
|
+
export const Required = Template.bind({});
|
|
45
|
+
Required.args = {
|
|
46
|
+
required: true,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const Disabled = Template.bind({});
|
|
50
|
+
Disabled.args = {
|
|
51
|
+
disabled: true,
|
|
52
|
+
modelValue: { label: 'Dark Maul', value: 'darth_maul' },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const SlotOption = (args) => ({
|
|
56
|
+
components: { BaseRadioGroup },
|
|
57
|
+
setup() {
|
|
58
|
+
const value = ref(null);
|
|
59
|
+
|
|
60
|
+
const options = [
|
|
61
|
+
{ label: 'Red', value: 'red' },
|
|
62
|
+
{ label: 'Blue', value: 'blue' },
|
|
63
|
+
{ label: 'Green', value: 'green' },
|
|
64
|
+
{ label: 'Black', value: 'black' },
|
|
65
|
+
{ label: 'Gray', value: 'gray' },
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
return { value, options, args };
|
|
69
|
+
},
|
|
70
|
+
template: `
|
|
71
|
+
<BaseRadioGroup
|
|
72
|
+
v-bind="args"
|
|
73
|
+
v-model="value"
|
|
74
|
+
:options="options"
|
|
75
|
+
label-class="flex space-x-2 items-start"
|
|
76
|
+
>
|
|
77
|
+
<template #option="{ option }">
|
|
78
|
+
<div>
|
|
79
|
+
<div class="flex items-center space-x-1">
|
|
80
|
+
<div style="height: 10px; width: 10px; border-radius: 4px;" :style="{ backgroundColor: option.value }"></div>
|
|
81
|
+
<div class="leading-none font-medium text-sm">{{ option.label }}</div>
|
|
82
|
+
</div>
|
|
83
|
+
<p class="text-slate-500 text-xs leading-tight">{{ option.value }}</p>
|
|
84
|
+
</div>
|
|
85
|
+
</template>
|
|
86
|
+
</BaseRadioGroup>
|
|
87
|
+
`,
|
|
88
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<ul class="space-y-3">
|
|
4
|
+
<li v-for="option in normalizedOptions" :key="option.value">
|
|
5
|
+
<label
|
|
6
|
+
:for="name + '-' + option.value"
|
|
7
|
+
class="cursor-pointer"
|
|
8
|
+
:class="[labelClass, disabled ? 'cursor-not-allowed opacity-50' : '']"
|
|
9
|
+
>
|
|
10
|
+
<input
|
|
11
|
+
:id="name + '-' + option.value"
|
|
12
|
+
type="radio"
|
|
13
|
+
:name="name"
|
|
14
|
+
:checked="isSelected(option)"
|
|
15
|
+
:required="required"
|
|
16
|
+
:disabled="disabled"
|
|
17
|
+
:value="option.value"
|
|
18
|
+
:class="inputClass"
|
|
19
|
+
@input="emit('update:modelValue', option.option)"
|
|
20
|
+
/>
|
|
21
|
+
|
|
22
|
+
<slot name="option" :option="option">
|
|
23
|
+
<span class="text-sm">{{ option.label }}</span>
|
|
24
|
+
</slot>
|
|
25
|
+
</label>
|
|
26
|
+
</li>
|
|
27
|
+
</ul>
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script lang="ts" setup>
|
|
32
|
+
import { PropType } from 'vue';
|
|
33
|
+
import { Option } from '@/types/types';
|
|
34
|
+
import { useHasOptions } from '@/composables/hasOptions';
|
|
35
|
+
|
|
36
|
+
const props = defineProps({
|
|
37
|
+
modelValue: {
|
|
38
|
+
default: undefined,
|
|
39
|
+
type: [Object, null] as PropType<Option | undefined>,
|
|
40
|
+
},
|
|
41
|
+
name: {
|
|
42
|
+
required: true,
|
|
43
|
+
type: String,
|
|
44
|
+
},
|
|
45
|
+
required: {
|
|
46
|
+
default: false,
|
|
47
|
+
type: Boolean,
|
|
48
|
+
},
|
|
49
|
+
disabled: {
|
|
50
|
+
default: false,
|
|
51
|
+
type: Boolean,
|
|
52
|
+
},
|
|
53
|
+
options: {
|
|
54
|
+
required: true,
|
|
55
|
+
type: Array as PropType<Option[]>,
|
|
56
|
+
},
|
|
57
|
+
labelKey: {
|
|
58
|
+
required: true,
|
|
59
|
+
type: String,
|
|
60
|
+
},
|
|
61
|
+
valueKey: {
|
|
62
|
+
required: true,
|
|
63
|
+
type: String,
|
|
64
|
+
},
|
|
65
|
+
labelClass: {
|
|
66
|
+
default: 'flex space-x-2',
|
|
67
|
+
type: String,
|
|
68
|
+
},
|
|
69
|
+
inputClass: {
|
|
70
|
+
default: 'mt-0.5 border-slate-300',
|
|
71
|
+
type: String,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const emit = defineEmits(['update:modelValue']);
|
|
76
|
+
|
|
77
|
+
const { normalizedOptions, isSelected } = useHasOptions(
|
|
78
|
+
computed(() => props.modelValue),
|
|
79
|
+
computed(() => props.options),
|
|
80
|
+
computed(() => props.labelKey),
|
|
81
|
+
computed(() => props.valueKey),
|
|
82
|
+
computed(() => false)
|
|
83
|
+
);
|
|
84
|
+
</script>
|
|
@@ -64,16 +64,14 @@ export const SlotOption = (args) => ({
|
|
|
64
64
|
v-model="value"
|
|
65
65
|
:options="options"
|
|
66
66
|
>
|
|
67
|
-
<template #option="{ option, active
|
|
67
|
+
<template #option="{ option, active }">
|
|
68
68
|
<div
|
|
69
69
|
class="rounded px-2 font-semibold py-1 text-sm"
|
|
70
70
|
:class="{
|
|
71
|
-
'hover:bg-slate-100': !active
|
|
72
|
-
'bg-slate-200 hover:bg-slate-300': active
|
|
73
|
-
'bg-blue-500 text-white hover:bg-blue-600': !active && selected,
|
|
74
|
-
'bg-blue-600 text-white hover:bg-blue-700': active && selected,
|
|
71
|
+
'hover:bg-slate-100': !active,
|
|
72
|
+
'bg-slate-200 hover:bg-slate-300': active,
|
|
75
73
|
}"
|
|
76
|
-
:style="{ color:
|
|
74
|
+
:style="{ color: option.value }"
|
|
77
75
|
>
|
|
78
76
|
{{ option.label }}
|
|
79
77
|
</div>
|