sprintify-ui 0.12.16 → 0.12.18
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 +6815 -6769
- package/dist/style.css +1 -1
- package/dist/types/components/BaseSignature.vue.d.ts +3 -0
- package/dist/types/index.d.ts +2 -0
- package/package.json +2 -1
- package/src/components/BaseSignature.mdx +26 -2
- package/src/components/BaseSignature.stories.js +40 -0
- package/src/components/BaseSignature.vue +94 -13
- package/src/lang/en.json +1 -0
- package/src/lang/fr.json +1 -0
|
@@ -43,6 +43,7 @@ export default {
|
|
|
43
43
|
height: 180,
|
|
44
44
|
disabled: false,
|
|
45
45
|
showClearButton: true,
|
|
46
|
+
signatureText: '',
|
|
46
47
|
},
|
|
47
48
|
argTypes: {
|
|
48
49
|
modelValue: {
|
|
@@ -65,6 +66,10 @@ export default {
|
|
|
65
66
|
control: "boolean",
|
|
66
67
|
description: "Shows the built-in clear button below the pad.",
|
|
67
68
|
},
|
|
69
|
+
signatureText: {
|
|
70
|
+
control: "text",
|
|
71
|
+
description: "Text rendered as a Dancing Script PNG when the built-in button is pressed.",
|
|
72
|
+
},
|
|
68
73
|
"update:modelValue": {
|
|
69
74
|
action: "update:modelValue",
|
|
70
75
|
table: { category: "Events" },
|
|
@@ -201,6 +206,41 @@ Compact.args = {
|
|
|
201
206
|
height: 120,
|
|
202
207
|
};
|
|
203
208
|
|
|
209
|
+
const TextSignatureTemplate = (args) => ({
|
|
210
|
+
components: { BaseSignature },
|
|
211
|
+
setup() {
|
|
212
|
+
const signature = ref(null);
|
|
213
|
+
const signatureText = ref(args.signatureText);
|
|
214
|
+
|
|
215
|
+
return { args, signature, signatureText };
|
|
216
|
+
},
|
|
217
|
+
template: `
|
|
218
|
+
<div class="space-y-3">
|
|
219
|
+
<label class="block max-w-md space-y-1 text-sm font-medium text-slate-700">
|
|
220
|
+
Signature text
|
|
221
|
+
<input
|
|
222
|
+
v-model="signatureText"
|
|
223
|
+
type="text"
|
|
224
|
+
class="block w-full rounded-md border-slate-300 shadow-sm"
|
|
225
|
+
>
|
|
226
|
+
</label>
|
|
227
|
+
<BaseSignature
|
|
228
|
+
v-bind="args"
|
|
229
|
+
v-model="signature"
|
|
230
|
+
:signature-text="signatureText"
|
|
231
|
+
/>
|
|
232
|
+
<p class="max-w-lg text-sm text-slate-600">
|
|
233
|
+
Applying the text replaces the current canvas content with a PNG signature.
|
|
234
|
+
</p>
|
|
235
|
+
</div>
|
|
236
|
+
`,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
export const TextSignature = TextSignatureTemplate.bind({});
|
|
240
|
+
TextSignature.args = {
|
|
241
|
+
signatureText: 'François Lévesque',
|
|
242
|
+
};
|
|
243
|
+
|
|
204
244
|
export const Disabled = ExistingSignatureTemplate.bind({});
|
|
205
245
|
Disabled.args = {
|
|
206
246
|
disabled: true,
|
|
@@ -24,36 +24,48 @@
|
|
|
24
24
|
/>
|
|
25
25
|
</div>
|
|
26
26
|
|
|
27
|
-
<div
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
<div v-if="showApplySignatureButton || showClearButton" class="btn-group w-full">
|
|
28
|
+
<BaseButton
|
|
29
|
+
v-if="showApplySignatureButton"
|
|
30
|
+
type="button"
|
|
31
|
+
size="sm"
|
|
32
|
+
class="w-full"
|
|
33
|
+
color="slate-100-outline"
|
|
34
|
+
icon="ph:signature-light"
|
|
35
|
+
:disabled="disabled || applyingTextSignature"
|
|
36
|
+
:loading="applyingTextSignature"
|
|
37
|
+
@click="applyTextSignature"
|
|
35
38
|
>
|
|
36
|
-
{{
|
|
37
|
-
</
|
|
38
|
-
<span v-else />
|
|
39
|
+
{{ t('sui.apply_signature') }}
|
|
40
|
+
</BaseButton>
|
|
39
41
|
|
|
40
42
|
<BaseButton
|
|
41
43
|
v-if="showClearButton"
|
|
42
44
|
type="button"
|
|
43
45
|
size="sm"
|
|
44
|
-
|
|
45
|
-
icon="
|
|
46
|
+
class="w-full"
|
|
47
|
+
icon="mdi:remove"
|
|
48
|
+
color="slate-100-outline"
|
|
46
49
|
:disabled="disabled || !canClear"
|
|
47
50
|
@click="clear"
|
|
48
51
|
>
|
|
49
52
|
{{ t('sui.clear') }}
|
|
50
53
|
</BaseButton>
|
|
51
54
|
</div>
|
|
55
|
+
|
|
56
|
+
<p
|
|
57
|
+
v-if="errorMessage"
|
|
58
|
+
role="alert"
|
|
59
|
+
class="text-sm mt-1 leading-tight text-red-600"
|
|
60
|
+
>
|
|
61
|
+
{{ errorMessage }}
|
|
62
|
+
</p>
|
|
52
63
|
</div>
|
|
53
64
|
</template>
|
|
54
65
|
|
|
55
66
|
<script lang="ts" setup>
|
|
56
67
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
68
|
+
import '@fontsource/dancing-script/400.css';
|
|
57
69
|
import { BaseButton } from '.';
|
|
58
70
|
import { t } from '@/i18n';
|
|
59
71
|
import type { SignatureError, SignatureErrorCode } from '@/types';
|
|
@@ -71,12 +83,14 @@ const props = withDefaults(defineProps<{
|
|
|
71
83
|
height?: number;
|
|
72
84
|
disabled?: boolean;
|
|
73
85
|
showClearButton?: boolean;
|
|
86
|
+
signatureText?: string;
|
|
74
87
|
}>(), {
|
|
75
88
|
modelValue: null,
|
|
76
89
|
width: 400,
|
|
77
90
|
height: 180,
|
|
78
91
|
disabled: false,
|
|
79
92
|
showClearButton: true,
|
|
93
|
+
signatureText: '',
|
|
80
94
|
});
|
|
81
95
|
|
|
82
96
|
const emit = defineEmits<{
|
|
@@ -93,6 +107,7 @@ const PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
|
93
107
|
const canvas = ref<HTMLCanvasElement | null>(null);
|
|
94
108
|
const errorMessage = ref<string | null>(null);
|
|
95
109
|
const filled = ref(false);
|
|
110
|
+
const applyingTextSignature = ref(false);
|
|
96
111
|
|
|
97
112
|
let context: CanvasRenderingContext2D | null = null;
|
|
98
113
|
let activePointerId: number | null = null;
|
|
@@ -107,6 +122,8 @@ const normalizedHeight = computed(() => Math.max(1, Math.round(props.height)));
|
|
|
107
122
|
const canClear = computed(() => {
|
|
108
123
|
return filled.value || Boolean(props.modelValue) || Boolean(errorMessage.value);
|
|
109
124
|
});
|
|
125
|
+
const signatureText = computed(() => props.signatureText.trim());
|
|
126
|
+
const showApplySignatureButton = computed(() => Boolean(signatureText.value));
|
|
110
127
|
|
|
111
128
|
const containerStyle = computed(() => ({
|
|
112
129
|
width: `${normalizedWidth.value}px`,
|
|
@@ -189,6 +206,69 @@ function resetCanvas() {
|
|
|
189
206
|
filled.value = false;
|
|
190
207
|
}
|
|
191
208
|
|
|
209
|
+
async function applyTextSignature() {
|
|
210
|
+
if (!context || props.disabled || !signatureText.value) return;
|
|
211
|
+
|
|
212
|
+
const text = signatureText.value;
|
|
213
|
+
const requestId = ++loadRequestId;
|
|
214
|
+
|
|
215
|
+
cancelDrawing();
|
|
216
|
+
errorMessage.value = null;
|
|
217
|
+
resetCanvas();
|
|
218
|
+
applyingTextSignature.value = true;
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
await document.fonts?.load('400 16px "Dancing Script"', text);
|
|
222
|
+
|
|
223
|
+
if (requestId !== loadRequestId || props.disabled) return;
|
|
224
|
+
|
|
225
|
+
drawTextSignature(text);
|
|
226
|
+
emitSignature();
|
|
227
|
+
} finally {
|
|
228
|
+
applyingTextSignature.value = false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function drawTextSignature(text: string) {
|
|
233
|
+
if (!context) return;
|
|
234
|
+
|
|
235
|
+
const availableWidth = normalizedWidth.value * (1 - SIGNATURE_PADDING_RATIO * 2);
|
|
236
|
+
const availableHeight = normalizedHeight.value * (1 - SIGNATURE_PADDING_RATIO * 2);
|
|
237
|
+
const maximumFontSize = Math.max(1, availableHeight * 2);
|
|
238
|
+
let minimumFontSize = 1;
|
|
239
|
+
let maximumFittingFontSize = maximumFontSize;
|
|
240
|
+
|
|
241
|
+
for (let index = 0; index < 20; index += 1) {
|
|
242
|
+
const fontSize = (minimumFontSize + maximumFittingFontSize) / 2;
|
|
243
|
+
|
|
244
|
+
context.font = `400 ${fontSize}px "Dancing Script", cursive`;
|
|
245
|
+
|
|
246
|
+
const metrics = context.measureText(text);
|
|
247
|
+
const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
|
248
|
+
const fits = metrics.width <= availableWidth && textHeight <= availableHeight;
|
|
249
|
+
|
|
250
|
+
if (fits) {
|
|
251
|
+
minimumFontSize = fontSize;
|
|
252
|
+
} else {
|
|
253
|
+
maximumFittingFontSize = fontSize;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
context.font = `400 ${minimumFontSize}px "Dancing Script", cursive`;
|
|
258
|
+
|
|
259
|
+
const metrics = context.measureText(text);
|
|
260
|
+
const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
|
261
|
+
const x = (normalizedWidth.value - metrics.width) / 2;
|
|
262
|
+
const y = (normalizedHeight.value - textHeight) / 2 + metrics.actualBoundingBoxAscent;
|
|
263
|
+
|
|
264
|
+
context.save();
|
|
265
|
+
context.fillStyle = '#000000';
|
|
266
|
+
context.textBaseline = 'alphabetic';
|
|
267
|
+
context.fillText(text, x, y);
|
|
268
|
+
context.restore();
|
|
269
|
+
filled.value = true;
|
|
270
|
+
}
|
|
271
|
+
|
|
192
272
|
async function renderModelValue(modelValue: string | null | undefined) {
|
|
193
273
|
const requestId = ++loadRequestId;
|
|
194
274
|
errorMessage.value = null;
|
|
@@ -356,6 +436,7 @@ function startDrawing(event: PointerEvent) {
|
|
|
356
436
|
|
|
357
437
|
event.preventDefault();
|
|
358
438
|
loadRequestId += 1;
|
|
439
|
+
applyingTextSignature.value = false;
|
|
359
440
|
activePointerId = event.pointerId;
|
|
360
441
|
drawing = true;
|
|
361
442
|
errorMessage.value = null;
|
package/src/lang/en.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"address_2_description": "Apartment, suite, unit, building",
|
|
13
13
|
"and": "and",
|
|
14
14
|
"apply": "Apply",
|
|
15
|
+
"apply_signature": "Apply my signature",
|
|
15
16
|
"apply_filters": "Apply filters",
|
|
16
17
|
"authentication_code": "Authentication code",
|
|
17
18
|
"autocomplete_placeholder": "Search...",
|
package/src/lang/fr.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"address_2_description": "Appartement, suite, unité, immeuble",
|
|
13
13
|
"and": "et",
|
|
14
14
|
"apply": "Appliquer",
|
|
15
|
+
"apply_signature": "Appliquer ma signature",
|
|
15
16
|
"apply_filters": "Appliquer les filtres",
|
|
16
17
|
"authentication_code": "Code d'authentification",
|
|
17
18
|
"autocomplete_placeholder": "Rechercher...",
|