sprintify-ui 0.6.33 → 0.6.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 +9161 -9108
- package/dist/style.css +1 -1
- package/dist/tailwindcss/button.js +1 -0
- package/dist/types/src/components/BaseInput.vue.d.ts +18 -0
- package/dist/types/src/components/BasePassword.vue.d.ts +13 -0
- package/dist/types/src/components/BaseTextarea.vue.d.ts +18 -0
- package/dist/types/src/components/BaseTextareaAutoresize.vue.d.ts +9 -0
- package/package.json +1 -1
- package/src/components/BaseDataTable.vue +5 -5
- package/src/components/BaseDataTableRowAction.vue +6 -6
- package/src/components/BaseDatePicker.vue +6 -3
- package/src/components/BaseDraggable.vue +5 -1
- package/src/components/BaseInput.stories.js +3 -3
- package/src/components/BaseInput.vue +60 -15
- package/src/components/BasePassword.stories.js +25 -0
- package/src/components/BasePassword.vue +35 -55
- package/src/components/BaseTextarea.stories.js +25 -0
- package/src/components/BaseTextarea.vue +34 -3
- package/src/components/BaseTextareaAutoresize.stories.js +27 -2
- package/src/components/BaseTextareaAutoresize.vue +27 -8
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div ref="elementsRef">
|
|
3
3
|
<slot
|
|
4
4
|
v-for="(element, index) in modelValue"
|
|
5
|
-
:key="element
|
|
5
|
+
:key="getKey(element, index)"
|
|
6
6
|
name="item"
|
|
7
7
|
:element="element"
|
|
8
8
|
:index="index"
|
|
@@ -25,6 +25,10 @@ const emit = defineEmits(['update:modelValue']);
|
|
|
25
25
|
|
|
26
26
|
const elementsRef = ref<HTMLElement | null>(null);
|
|
27
27
|
|
|
28
|
+
function getKey(element: any, index: number) {
|
|
29
|
+
return element[props.itemKey] ?? index;
|
|
30
|
+
}
|
|
31
|
+
|
|
28
32
|
let sortable = null as Sortable | null;
|
|
29
33
|
|
|
30
34
|
onMounted(() => {
|
|
@@ -60,7 +60,7 @@ IconLeft.args = {
|
|
|
60
60
|
|
|
61
61
|
export const IconRight = Template.bind({});
|
|
62
62
|
IconRight.args = {
|
|
63
|
-
iconRight: 'mdi:email
|
|
63
|
+
iconRight: 'mdi:email',
|
|
64
64
|
placeholder: 'Enter your email',
|
|
65
65
|
};
|
|
66
66
|
|
|
@@ -169,7 +169,7 @@ export const Field = createFieldStory({
|
|
|
169
169
|
});
|
|
170
170
|
|
|
171
171
|
const TemplateSizes = (args) => ({
|
|
172
|
-
components: { BaseInput
|
|
172
|
+
components: { BaseInput },
|
|
173
173
|
setup() {
|
|
174
174
|
const value = ref(null);
|
|
175
175
|
const sizes = ['xs', 'sm', 'md'];
|
|
@@ -177,7 +177,7 @@ const TemplateSizes = (args) => ({
|
|
|
177
177
|
},
|
|
178
178
|
template: `
|
|
179
179
|
<div v-for="size in sizes" :key="size" class="mb-4">
|
|
180
|
-
<p class="text-xs text-slate-600 leading-tight mb-1">
|
|
180
|
+
<p class="text-xs text-slate-600 leading-tight mb-1">{{ size }}</p>
|
|
181
181
|
<BaseInput v-model="value" v-bind="args" :size="size" class="w-full"></BaseInput>
|
|
182
182
|
</div>
|
|
183
183
|
`,
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :class="classes">
|
|
3
3
|
<div :class="decorationWrapClasses">
|
|
4
|
-
<
|
|
4
|
+
<component
|
|
5
|
+
:is="hasLeftListener ? 'button' : 'div'"
|
|
5
6
|
v-if="iconLeft"
|
|
6
|
-
:
|
|
7
|
+
:type="hasLeftListener ? 'button' : undefined"
|
|
8
|
+
:class="[decorationClasses, hasLeftListener ? 'hover:bg-slate-100' : '']"
|
|
9
|
+
@click="onIconLeftClickInternal"
|
|
7
10
|
>
|
|
8
11
|
<BaseIcon
|
|
9
12
|
:icon="iconLeft"
|
|
10
13
|
:class="iconClasses"
|
|
11
14
|
/>
|
|
12
|
-
</
|
|
15
|
+
</component>
|
|
13
16
|
<div
|
|
14
17
|
v-if="prefix"
|
|
15
18
|
:class="decorationClasses"
|
|
19
|
+
@click="focusAction"
|
|
16
20
|
>
|
|
17
21
|
{{ prefix }}
|
|
18
22
|
</div>
|
|
@@ -47,18 +51,22 @@
|
|
|
47
51
|
<div
|
|
48
52
|
v-if="suffix"
|
|
49
53
|
:class="decorationClasses"
|
|
54
|
+
@click="focusAction"
|
|
50
55
|
>
|
|
51
56
|
{{ suffix }}
|
|
52
57
|
</div>
|
|
53
|
-
<
|
|
58
|
+
<component
|
|
59
|
+
:is="hasRightListener ? 'button' : 'div'"
|
|
54
60
|
v-if="iconRight"
|
|
55
|
-
:
|
|
61
|
+
:type="hasRightListener ? 'button' : undefined"
|
|
62
|
+
:class="[decorationClasses, hasRightListener ? 'hover:bg-slate-100' : '']"
|
|
63
|
+
@click="onIconRightClickInternal"
|
|
56
64
|
>
|
|
57
65
|
<BaseIcon
|
|
58
66
|
:icon="iconRight"
|
|
59
67
|
:class="iconClasses"
|
|
60
68
|
/>
|
|
61
|
-
</
|
|
69
|
+
</component>
|
|
62
70
|
</div>
|
|
63
71
|
</div>
|
|
64
72
|
</template>
|
|
@@ -162,6 +170,14 @@ const props = defineProps({
|
|
|
162
170
|
default: true,
|
|
163
171
|
type: Boolean,
|
|
164
172
|
},
|
|
173
|
+
onIconLeftClick: {
|
|
174
|
+
default: undefined,
|
|
175
|
+
type: Function as PropType<() => void>,
|
|
176
|
+
},
|
|
177
|
+
onIconRightClick: {
|
|
178
|
+
default: undefined,
|
|
179
|
+
type: Function as PropType<() => void>,
|
|
180
|
+
},
|
|
165
181
|
});
|
|
166
182
|
|
|
167
183
|
const input = ref<HTMLInputElement | null>(null);
|
|
@@ -175,6 +191,14 @@ const hasRightDecoration = computed(() => {
|
|
|
175
191
|
return props.iconRight || props.suffix;
|
|
176
192
|
});
|
|
177
193
|
|
|
194
|
+
const hasLeftListener = computed(() => {
|
|
195
|
+
return props.onIconLeftClick !== undefined;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const hasRightListener = computed(() => {
|
|
199
|
+
return props.onIconRightClick !== undefined;
|
|
200
|
+
});
|
|
201
|
+
|
|
178
202
|
const maskOptions = computed(() => {
|
|
179
203
|
if (props.mask) {
|
|
180
204
|
return {
|
|
@@ -287,9 +311,9 @@ const baseClasses = computed(() => {
|
|
|
287
311
|
const disabled = props.disabled ? 'cursor-not-allowed text-slate-300' : '';
|
|
288
312
|
|
|
289
313
|
const paddingX = {
|
|
290
|
-
xs: [hasLeftDecoration.value ? 'pl-
|
|
291
|
-
sm: [hasLeftDecoration.value ? 'pl-
|
|
292
|
-
md: [hasLeftDecoration.value ? 'pl-
|
|
314
|
+
xs: [hasLeftDecoration.value ? 'pl-0.5' : 'pl-2', hasRightDecoration.value ? 'pr-0' : 'pr-2'],
|
|
315
|
+
sm: [hasLeftDecoration.value ? 'pl-0.5' : 'pl-2.5', hasRightDecoration.value ? 'pr-0' : 'pr-2.5'],
|
|
316
|
+
md: [hasLeftDecoration.value ? 'pl-1' : 'pl-3', hasRightDecoration.value ? 'pr-1' : 'pr-3'],
|
|
293
317
|
}[sizeInternal.value];
|
|
294
318
|
|
|
295
319
|
return [
|
|
@@ -301,11 +325,11 @@ const baseClasses = computed(() => {
|
|
|
301
325
|
});
|
|
302
326
|
|
|
303
327
|
const decorationWrapClasses = computed(() => {
|
|
304
|
-
const base = `flex
|
|
328
|
+
const base = `flex justify-center empty:hidden`;
|
|
305
329
|
const spacing = {
|
|
306
|
-
xs: 'first:pl-
|
|
307
|
-
sm: 'first:pl-
|
|
308
|
-
md: 'first:pl-
|
|
330
|
+
xs: 'first:pl-0.5 last:pr-0.5 py-0.5',
|
|
331
|
+
sm: 'first:pl-1 last:pr-1 py-1',
|
|
332
|
+
md: 'first:pl-1 last:pr-1 py-1',
|
|
309
333
|
}[sizeInternal.value];
|
|
310
334
|
|
|
311
335
|
return [
|
|
@@ -315,10 +339,15 @@ const decorationWrapClasses = computed(() => {
|
|
|
315
339
|
});
|
|
316
340
|
|
|
317
341
|
const decorationClasses = computed(() => {
|
|
318
|
-
const base = `flex items-center justify-center`;
|
|
342
|
+
const base = `flex items-center justify-center rounded-md`;
|
|
319
343
|
const textColor = hasErrorInternal.value ? 'text-red-800' : 'text-slate-500';
|
|
344
|
+
const padding = {
|
|
345
|
+
xs: 'p-1',
|
|
346
|
+
sm: 'p-1.5',
|
|
347
|
+
md: 'p-2',
|
|
348
|
+
}[sizeInternal.value];
|
|
320
349
|
|
|
321
|
-
return `${base} ${textColor}`;
|
|
350
|
+
return `${base} ${textColor} ${padding}`;
|
|
322
351
|
});
|
|
323
352
|
|
|
324
353
|
const iconClasses = computed(() => {
|
|
@@ -337,6 +366,22 @@ function blurAction() {
|
|
|
337
366
|
input.value?.blur();
|
|
338
367
|
}
|
|
339
368
|
|
|
369
|
+
function onIconLeftClickInternal() {
|
|
370
|
+
if (props.onIconLeftClick) {
|
|
371
|
+
props.onIconLeftClick();
|
|
372
|
+
} else {
|
|
373
|
+
focusAction();
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function onIconRightClickInternal() {
|
|
378
|
+
if (props.onIconRightClick) {
|
|
379
|
+
props.onIconRightClick();
|
|
380
|
+
} else {
|
|
381
|
+
blurAction();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
340
385
|
defineExpose({
|
|
341
386
|
focus: focusAction,
|
|
342
387
|
blur: blurAction,
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import BasePassword from './BasePassword.vue';
|
|
2
2
|
import { createFieldStory } from '@/../.storybook/utils';
|
|
3
3
|
|
|
4
|
+
const sizes = ['xs', 'sm', 'md'];
|
|
5
|
+
|
|
4
6
|
export default {
|
|
5
7
|
title: 'Form/BasePassword',
|
|
6
8
|
component: BasePassword,
|
|
7
9
|
args: {},
|
|
10
|
+
argTypes: {
|
|
11
|
+
size: {
|
|
12
|
+
control: { type: 'select' },
|
|
13
|
+
options: sizes,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
8
16
|
};
|
|
9
17
|
|
|
10
18
|
const Template = (args) => ({
|
|
@@ -55,4 +63,21 @@ const FocusTemplate = (args) => ({
|
|
|
55
63
|
`,
|
|
56
64
|
});
|
|
57
65
|
|
|
66
|
+
const TemplateSizes = (args) => ({
|
|
67
|
+
components: { BasePassword },
|
|
68
|
+
setup() {
|
|
69
|
+
const value = ref(null);
|
|
70
|
+
const sizes = ['xs', 'sm', 'md'];
|
|
71
|
+
return { args, value, sizes };
|
|
72
|
+
},
|
|
73
|
+
template: `
|
|
74
|
+
<div v-for="size in sizes" :key="size" class="mb-4">
|
|
75
|
+
<p class="text-xs text-slate-600 leading-tight mb-1">{{ size }}</p>
|
|
76
|
+
<BasePassword v-model="value" v-bind="args" :size="size" class="w-full"></BasePassword>
|
|
77
|
+
</div>
|
|
78
|
+
`,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export const Sizes = TemplateSizes.bind({});
|
|
82
|
+
|
|
58
83
|
export const Focus = FocusTemplate.bind({});
|
|
@@ -1,50 +1,25 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
:placeholder="placeholder"
|
|
16
|
-
:required="requiredInternal"
|
|
17
|
-
class="grow rounded-l rounded-r-none border-none focus:ring-2 focus:ring-primary-500 disabled:cursor-not-allowed"
|
|
18
|
-
@input="onInput"
|
|
19
|
-
>
|
|
20
|
-
<div class="flex shrink-0 pl-3">
|
|
21
|
-
<button
|
|
22
|
-
tabindex="-1"
|
|
23
|
-
type="button"
|
|
24
|
-
class="pr-3 text-slate-500 disabled:cursor-not-allowed disabled:text-slate-300"
|
|
25
|
-
:disabled="disabled"
|
|
26
|
-
@click="showPassword = !showPassword"
|
|
27
|
-
>
|
|
28
|
-
<BaseIcon
|
|
29
|
-
v-if="!showPassword"
|
|
30
|
-
icon="heroicons:eye-slash-20-solid"
|
|
31
|
-
class="h-5 w-5"
|
|
32
|
-
/>
|
|
33
|
-
<BaseIcon
|
|
34
|
-
v-else
|
|
35
|
-
icon="heroicons:eye-20-solid"
|
|
36
|
-
class="h-5 w-5"
|
|
37
|
-
/>
|
|
38
|
-
</button>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
2
|
+
<BaseInput
|
|
3
|
+
ref="input"
|
|
4
|
+
:model-value="modelValue"
|
|
5
|
+
:type="showPassword ? 'text' : 'password'"
|
|
6
|
+
:disabled="disabled"
|
|
7
|
+
:placeholder="placeholder"
|
|
8
|
+
:size="size"
|
|
9
|
+
:icon-right="showPassword ? 'heroicons:eye-20-solid' : 'heroicons:eye-slash-20-solid'"
|
|
10
|
+
@icon-right-click="onIconRightClick"
|
|
11
|
+
@update:model-value="onUpdateModelValue"
|
|
12
|
+
@focus="onFocus"
|
|
13
|
+
@blur="onBlur"
|
|
14
|
+
/>
|
|
41
15
|
</template>
|
|
42
16
|
|
|
43
17
|
<script lang="ts" setup>
|
|
44
|
-
import { trim } from 'lodash';
|
|
45
|
-
import { Icon as BaseIcon } from '@iconify/vue';
|
|
46
18
|
import { PropType } from 'vue';
|
|
47
|
-
import {
|
|
19
|
+
import { Size } from '@/utils/sizes';
|
|
20
|
+
import BaseInput from './BaseInput.vue';
|
|
21
|
+
|
|
22
|
+
const emit = defineEmits(['update:modelValue', 'blur', 'focus']);
|
|
48
23
|
|
|
49
24
|
const props = defineProps({
|
|
50
25
|
modelValue: {
|
|
@@ -55,6 +30,10 @@ const props = defineProps({
|
|
|
55
30
|
default: false,
|
|
56
31
|
type: Boolean,
|
|
57
32
|
},
|
|
33
|
+
size: {
|
|
34
|
+
default: undefined,
|
|
35
|
+
type: String as PropType<Size>,
|
|
36
|
+
},
|
|
58
37
|
name: {
|
|
59
38
|
default: undefined,
|
|
60
39
|
type: String,
|
|
@@ -75,21 +54,10 @@ const props = defineProps({
|
|
|
75
54
|
|
|
76
55
|
const input = ref<HTMLInputElement | null>(null);
|
|
77
56
|
|
|
78
|
-
const emit = defineEmits(['update:modelValue']);
|
|
79
|
-
|
|
80
|
-
const { nameInternal, requiredInternal, hasErrorInternal, emitUpdate } =
|
|
81
|
-
useField({
|
|
82
|
-
name: computed(() => props.name),
|
|
83
|
-
required: computed(() => props.required),
|
|
84
|
-
hasError: computed(() => props.hasError),
|
|
85
|
-
emit: emit,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
57
|
const showPassword = ref(false);
|
|
89
58
|
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
emitUpdate(trim(value));
|
|
59
|
+
function onIconRightClick() {
|
|
60
|
+
showPassword.value = !showPassword.value;
|
|
93
61
|
}
|
|
94
62
|
|
|
95
63
|
function focus() {
|
|
@@ -100,6 +68,18 @@ function blur() {
|
|
|
100
68
|
input.value?.blur();
|
|
101
69
|
}
|
|
102
70
|
|
|
71
|
+
function onUpdateModelValue(value: string) {
|
|
72
|
+
emit('update:modelValue', value);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function onBlur() {
|
|
76
|
+
emit('blur');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function onFocus() {
|
|
80
|
+
emit('focus');
|
|
81
|
+
}
|
|
82
|
+
|
|
103
83
|
defineExpose({
|
|
104
84
|
focus,
|
|
105
85
|
blur,
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { createFieldStory } from '../../.storybook/utils';
|
|
2
2
|
import BaseTextarea from './BaseTextarea.vue';
|
|
3
3
|
|
|
4
|
+
const sizes = ['xs', 'sm', 'md'];
|
|
5
|
+
|
|
4
6
|
export default {
|
|
5
7
|
title: 'Form/BaseTextarea',
|
|
6
8
|
component: BaseTextarea,
|
|
7
9
|
args: {
|
|
8
10
|
placeholder: 'Describe your complete life in 4 sentences...',
|
|
9
11
|
},
|
|
12
|
+
argTypes: {
|
|
13
|
+
size: {
|
|
14
|
+
control: { type: 'select' },
|
|
15
|
+
options: sizes,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
10
18
|
};
|
|
11
19
|
|
|
12
20
|
const Template = (args) => ({
|
|
@@ -42,6 +50,23 @@ export const Field = createFieldStory({
|
|
|
42
50
|
label: 'Biography',
|
|
43
51
|
});
|
|
44
52
|
|
|
53
|
+
const TemplateSizes = (args) => ({
|
|
54
|
+
components: { BaseTextarea },
|
|
55
|
+
setup() {
|
|
56
|
+
const value = ref(null);
|
|
57
|
+
const sizes = ['xs', 'sm', 'md'];
|
|
58
|
+
return { args, value, sizes };
|
|
59
|
+
},
|
|
60
|
+
template: `
|
|
61
|
+
<div v-for="size in sizes" :key="size" class="mb-4">
|
|
62
|
+
<p class="text-xs text-slate-600 leading-tight mb-1">{{ size }}</p>
|
|
63
|
+
<BaseTextarea v-model="value" v-bind="args" :size="size" class="w-full"></BaseTextarea>
|
|
64
|
+
</div>
|
|
65
|
+
`,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const Sizes = TemplateSizes.bind({});
|
|
69
|
+
|
|
45
70
|
const FocusTemplate = (args) => ({
|
|
46
71
|
components: { BaseTextarea },
|
|
47
72
|
setup() {
|
|
@@ -8,17 +8,23 @@
|
|
|
8
8
|
:disabled="disabled"
|
|
9
9
|
:required="requiredInternal"
|
|
10
10
|
:rows="rows"
|
|
11
|
-
:
|
|
12
|
-
class="
|
|
11
|
+
:autocomplete="autocomplete ? 'on' : 'off'"
|
|
12
|
+
:class="classes"
|
|
13
13
|
@input="emitUpdate(transformInputValue($event))"
|
|
14
14
|
/>
|
|
15
15
|
</template>
|
|
16
16
|
|
|
17
17
|
<script lang="ts" setup>
|
|
18
18
|
import { useField } from '@/composables/field';
|
|
19
|
+
import { Size, sizes } from '@/utils/sizes';
|
|
19
20
|
import { get, isString } from 'lodash';
|
|
21
|
+
import { twMerge } from 'tailwind-merge';
|
|
20
22
|
import { PropType } from 'vue';
|
|
21
23
|
|
|
24
|
+
defineOptions({
|
|
25
|
+
inheritAttrs: false,
|
|
26
|
+
});
|
|
27
|
+
|
|
22
28
|
const props = defineProps({
|
|
23
29
|
modelValue: {
|
|
24
30
|
default: undefined,
|
|
@@ -28,6 +34,14 @@ const props = defineProps({
|
|
|
28
34
|
type: String,
|
|
29
35
|
default: 'text',
|
|
30
36
|
},
|
|
37
|
+
size: {
|
|
38
|
+
default: undefined,
|
|
39
|
+
type: String as PropType<Size>,
|
|
40
|
+
},
|
|
41
|
+
class: {
|
|
42
|
+
default: '',
|
|
43
|
+
type: [String, Array] as PropType<string | string[]>,
|
|
44
|
+
},
|
|
31
45
|
autocomplete: {
|
|
32
46
|
default: true,
|
|
33
47
|
type: Boolean,
|
|
@@ -64,10 +78,11 @@ const props = defineProps({
|
|
|
64
78
|
|
|
65
79
|
const emit = defineEmits(['update:modelValue']);
|
|
66
80
|
|
|
67
|
-
const { nameInternal, requiredInternal, hasErrorInternal, emitUpdate } =
|
|
81
|
+
const { nameInternal, requiredInternal, hasErrorInternal, emitUpdate, sizeInternal } =
|
|
68
82
|
useField({
|
|
69
83
|
name: computed(() => props.name),
|
|
70
84
|
required: computed(() => props.required),
|
|
85
|
+
size: computed(() => props.size),
|
|
71
86
|
hasError: computed(() => props.hasError),
|
|
72
87
|
emit: emit,
|
|
73
88
|
});
|
|
@@ -96,6 +111,22 @@ function blur() {
|
|
|
96
111
|
textareaRef.value?.blur();
|
|
97
112
|
}
|
|
98
113
|
|
|
114
|
+
const classes = computed(() => {
|
|
115
|
+
const base = 'mb-0 block input-rounded transition-colors duration-200';
|
|
116
|
+
const disabled = 'disabled:cursor-not-allowed disabled:text-slate-300';
|
|
117
|
+
const focus = 'focus:input-focus';
|
|
118
|
+
const error = hasErrorInternal.value ? 'border-red-500 focus:input-focus-error' : 'border-slate-300';
|
|
119
|
+
const sizeConfig = sizes[sizeInternal.value];
|
|
120
|
+
|
|
121
|
+
const padding = {
|
|
122
|
+
xs: 'p-2',
|
|
123
|
+
sm: 'p-2.5',
|
|
124
|
+
md: 'p-3',
|
|
125
|
+
}[sizeInternal.value];
|
|
126
|
+
|
|
127
|
+
return twMerge(base, disabled, error, focus, sizeConfig.fontSize, padding, props.class);
|
|
128
|
+
});
|
|
129
|
+
|
|
99
130
|
defineExpose({
|
|
100
131
|
focus,
|
|
101
132
|
blur,
|
|
@@ -2,6 +2,8 @@ import BaseTextareaAutoresize from './BaseTextareaAutoresize.vue';
|
|
|
2
2
|
import ShowValue from '@/../.storybook/components/ShowValue.vue';
|
|
3
3
|
import { createFieldStory } from '../../.storybook/utils';
|
|
4
4
|
|
|
5
|
+
const sizes = ['xs', 'sm', 'md'];
|
|
6
|
+
|
|
5
7
|
export default {
|
|
6
8
|
title: 'Form/BaseTextareaAutoresize',
|
|
7
9
|
component: BaseTextareaAutoresize,
|
|
@@ -9,6 +11,12 @@ export default {
|
|
|
9
11
|
name: 'name',
|
|
10
12
|
placeholder: 'Describe your complete life in 4 sentences...',
|
|
11
13
|
},
|
|
14
|
+
argTypes: {
|
|
15
|
+
size: {
|
|
16
|
+
control: { type: 'select' },
|
|
17
|
+
options: sizes,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
12
20
|
};
|
|
13
21
|
|
|
14
22
|
const Template = (args) => ({
|
|
@@ -68,8 +76,8 @@ const TemplateTWTextarea = (args) => ({
|
|
|
68
76
|
<BaseTextareaAutoresize
|
|
69
77
|
v-model="value"
|
|
70
78
|
v-bind="args"
|
|
71
|
-
class="w-full
|
|
72
|
-
tw-textarea="p-5 bg-slate-100 text-slate-700 focus:ring-
|
|
79
|
+
class="w-full"
|
|
80
|
+
tw-textarea="shadow-xl rounded-none p-5 duration-500 transition-all bg-slate-100 text-slate-900 ring-2 ring-purple-700 border-none ring-opacity-60 focus:ring-pink-500 focus:ring-4 focus:bg-pink-100"
|
|
73
81
|
@submit="onSubmit"
|
|
74
82
|
></BaseTextareaAutoresize>
|
|
75
83
|
</form>
|
|
@@ -86,6 +94,23 @@ export const Field = createFieldStory({
|
|
|
86
94
|
label: 'Biography',
|
|
87
95
|
});
|
|
88
96
|
|
|
97
|
+
const TemplateSizes = (args) => ({
|
|
98
|
+
components: { BaseTextareaAutoresize },
|
|
99
|
+
setup() {
|
|
100
|
+
const value = ref(null);
|
|
101
|
+
const sizes = ['xs', 'sm', 'md'];
|
|
102
|
+
return { args, value, sizes };
|
|
103
|
+
},
|
|
104
|
+
template: `
|
|
105
|
+
<div v-for="size in sizes" :key="size" class="mb-4">
|
|
106
|
+
<p class="text-xs text-slate-600 leading-tight mb-1">{{ size }}</p>
|
|
107
|
+
<BaseTextareaAutoresize v-model="value" v-bind="args" :size="size" class="w-full"></BaseTextareaAutoresize>
|
|
108
|
+
</div>
|
|
109
|
+
`,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export const Sizes = TemplateSizes.bind({});
|
|
113
|
+
|
|
89
114
|
const FocusTemplate = (args) => ({
|
|
90
115
|
components: { BaseTextareaAutoresize },
|
|
91
116
|
setup() {
|
|
@@ -36,12 +36,10 @@
|
|
|
36
36
|
|
|
37
37
|
<script lang="ts" setup>
|
|
38
38
|
import { useField } from '@/composables/field';
|
|
39
|
+
import { sizes, Size } from '@/utils/sizes';
|
|
39
40
|
import { twMerge } from 'tailwind-merge';
|
|
40
41
|
import { PropType } from 'vue';
|
|
41
42
|
|
|
42
|
-
const BASE_TEXTAREA_CLASSES =
|
|
43
|
-
'py-2 px-3 font-normal text-base disabled:cursor-not-allowed disabled:text-slate-300 font-sans rounded leading-normal tracking-normal border placeholder:text-slate-400 placeholder:font-light';
|
|
44
|
-
|
|
45
43
|
const BASE_GRID_AREA = '1 / 1 / 2 / 2';
|
|
46
44
|
|
|
47
45
|
/* Note the weird space! Needed to prevent jumpy behavior */
|
|
@@ -64,6 +62,10 @@ const props = defineProps({
|
|
|
64
62
|
default: false,
|
|
65
63
|
type: Boolean,
|
|
66
64
|
},
|
|
65
|
+
size: {
|
|
66
|
+
default: undefined,
|
|
67
|
+
type: String as PropType<Size>,
|
|
68
|
+
},
|
|
67
69
|
maxHeight: {
|
|
68
70
|
default: 100,
|
|
69
71
|
type: Number,
|
|
@@ -98,10 +100,11 @@ const emit = defineEmits(['update:modelValue', 'submit', 'focus', 'input']);
|
|
|
98
100
|
|
|
99
101
|
const textareaRef = ref<null | HTMLTextAreaElement>(null);
|
|
100
102
|
|
|
101
|
-
const { nameInternal, requiredInternal, hasErrorInternal } =
|
|
103
|
+
const { nameInternal, requiredInternal, hasErrorInternal, sizeInternal } =
|
|
102
104
|
useField({
|
|
103
105
|
name: computed(() => props.name),
|
|
104
106
|
required: computed(() => props.required),
|
|
107
|
+
size: computed(() => props.size),
|
|
105
108
|
hasError: computed(() => props.hasError),
|
|
106
109
|
emit: emit,
|
|
107
110
|
});
|
|
@@ -142,11 +145,27 @@ function onFocus(event: FocusEvent) {
|
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
const textareaClasses = computed(() => {
|
|
148
|
+
const base = 'py-2 px-3 input-rounded leading-normal tracking-normal border transition-colors duration-200';
|
|
149
|
+
const disabled = 'disabled:cursor-not-allowed disabled:text-slate-300';
|
|
150
|
+
const focus = 'focus:input-focus';
|
|
151
|
+
const error = hasErrorInternal.value ? 'border-red-500 focus:input-focus-error' : 'border-slate-300';
|
|
152
|
+
const placeholder = 'placeholder:text-slate-400 placeholder:font-light';
|
|
153
|
+
const sizeConfig = sizes[sizeInternal.value];
|
|
154
|
+
|
|
155
|
+
const padding = {
|
|
156
|
+
xs: 'px-2',
|
|
157
|
+
sm: 'px-2.5',
|
|
158
|
+
md: 'px-3',
|
|
159
|
+
}[sizeInternal.value];
|
|
160
|
+
|
|
145
161
|
return twMerge(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
162
|
+
base,
|
|
163
|
+
disabled,
|
|
164
|
+
error,
|
|
165
|
+
placeholder,
|
|
166
|
+
focus,
|
|
167
|
+
sizeConfig.fontSize,
|
|
168
|
+
padding,
|
|
150
169
|
props.twTextarea
|
|
151
170
|
);
|
|
152
171
|
});
|