sprintify-ui 0.12.16 → 0.12.17
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 +6818 -6773
- 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 +96 -12
- 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,
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
</div>
|
|
26
26
|
|
|
27
27
|
<div
|
|
28
|
-
v-if="showClearButton || errorMessage"
|
|
28
|
+
v-if="showApplySignatureButton || showClearButton || errorMessage"
|
|
29
29
|
class="flex items-start justify-between gap-3"
|
|
30
30
|
>
|
|
31
31
|
<p
|
|
@@ -37,23 +37,38 @@
|
|
|
37
37
|
</p>
|
|
38
38
|
<span v-else />
|
|
39
39
|
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
<div class="flex flex-wrap justify-end gap-2">
|
|
41
|
+
<BaseButton
|
|
42
|
+
v-if="showApplySignatureButton"
|
|
43
|
+
type="button"
|
|
44
|
+
size="sm"
|
|
45
|
+
color="secondary-outline"
|
|
46
|
+
:disabled="disabled || applyingTextSignature"
|
|
47
|
+
:loading="applyingTextSignature"
|
|
48
|
+
@click="applyTextSignature"
|
|
49
|
+
>
|
|
50
|
+
{{ t('sui.apply_signature') }}
|
|
51
|
+
</BaseButton>
|
|
52
|
+
|
|
53
|
+
<BaseButton
|
|
54
|
+
v-if="showClearButton"
|
|
55
|
+
type="button"
|
|
56
|
+
size="sm"
|
|
57
|
+
color="secondary-outline"
|
|
58
|
+
icon="heroicons:trash"
|
|
59
|
+
:disabled="disabled || !canClear"
|
|
60
|
+
@click="clear"
|
|
61
|
+
>
|
|
62
|
+
{{ t('sui.clear') }}
|
|
63
|
+
</BaseButton>
|
|
64
|
+
</div>
|
|
51
65
|
</div>
|
|
52
66
|
</div>
|
|
53
67
|
</template>
|
|
54
68
|
|
|
55
69
|
<script lang="ts" setup>
|
|
56
70
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
71
|
+
import '@fontsource/dancing-script/400.css';
|
|
57
72
|
import { BaseButton } from '.';
|
|
58
73
|
import { t } from '@/i18n';
|
|
59
74
|
import type { SignatureError, SignatureErrorCode } from '@/types';
|
|
@@ -71,12 +86,14 @@ const props = withDefaults(defineProps<{
|
|
|
71
86
|
height?: number;
|
|
72
87
|
disabled?: boolean;
|
|
73
88
|
showClearButton?: boolean;
|
|
89
|
+
signatureText?: string;
|
|
74
90
|
}>(), {
|
|
75
91
|
modelValue: null,
|
|
76
92
|
width: 400,
|
|
77
93
|
height: 180,
|
|
78
94
|
disabled: false,
|
|
79
95
|
showClearButton: true,
|
|
96
|
+
signatureText: '',
|
|
80
97
|
});
|
|
81
98
|
|
|
82
99
|
const emit = defineEmits<{
|
|
@@ -93,6 +110,7 @@ const PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
|
93
110
|
const canvas = ref<HTMLCanvasElement | null>(null);
|
|
94
111
|
const errorMessage = ref<string | null>(null);
|
|
95
112
|
const filled = ref(false);
|
|
113
|
+
const applyingTextSignature = ref(false);
|
|
96
114
|
|
|
97
115
|
let context: CanvasRenderingContext2D | null = null;
|
|
98
116
|
let activePointerId: number | null = null;
|
|
@@ -107,6 +125,8 @@ const normalizedHeight = computed(() => Math.max(1, Math.round(props.height)));
|
|
|
107
125
|
const canClear = computed(() => {
|
|
108
126
|
return filled.value || Boolean(props.modelValue) || Boolean(errorMessage.value);
|
|
109
127
|
});
|
|
128
|
+
const signatureText = computed(() => props.signatureText.trim());
|
|
129
|
+
const showApplySignatureButton = computed(() => Boolean(signatureText.value));
|
|
110
130
|
|
|
111
131
|
const containerStyle = computed(() => ({
|
|
112
132
|
width: `${normalizedWidth.value}px`,
|
|
@@ -189,6 +209,69 @@ function resetCanvas() {
|
|
|
189
209
|
filled.value = false;
|
|
190
210
|
}
|
|
191
211
|
|
|
212
|
+
async function applyTextSignature() {
|
|
213
|
+
if (!context || props.disabled || !signatureText.value) return;
|
|
214
|
+
|
|
215
|
+
const text = signatureText.value;
|
|
216
|
+
const requestId = ++loadRequestId;
|
|
217
|
+
|
|
218
|
+
cancelDrawing();
|
|
219
|
+
errorMessage.value = null;
|
|
220
|
+
resetCanvas();
|
|
221
|
+
applyingTextSignature.value = true;
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
await document.fonts?.load('400 16px "Dancing Script"', text);
|
|
225
|
+
|
|
226
|
+
if (requestId !== loadRequestId || props.disabled) return;
|
|
227
|
+
|
|
228
|
+
drawTextSignature(text);
|
|
229
|
+
emitSignature();
|
|
230
|
+
} finally {
|
|
231
|
+
applyingTextSignature.value = false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function drawTextSignature(text: string) {
|
|
236
|
+
if (!context) return;
|
|
237
|
+
|
|
238
|
+
const availableWidth = normalizedWidth.value * (1 - SIGNATURE_PADDING_RATIO * 2);
|
|
239
|
+
const availableHeight = normalizedHeight.value * (1 - SIGNATURE_PADDING_RATIO * 2);
|
|
240
|
+
const maximumFontSize = Math.max(1, availableHeight * 2);
|
|
241
|
+
let minimumFontSize = 1;
|
|
242
|
+
let maximumFittingFontSize = maximumFontSize;
|
|
243
|
+
|
|
244
|
+
for (let index = 0; index < 20; index += 1) {
|
|
245
|
+
const fontSize = (minimumFontSize + maximumFittingFontSize) / 2;
|
|
246
|
+
|
|
247
|
+
context.font = `400 ${fontSize}px "Dancing Script", cursive`;
|
|
248
|
+
|
|
249
|
+
const metrics = context.measureText(text);
|
|
250
|
+
const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
|
251
|
+
const fits = metrics.width <= availableWidth && textHeight <= availableHeight;
|
|
252
|
+
|
|
253
|
+
if (fits) {
|
|
254
|
+
minimumFontSize = fontSize;
|
|
255
|
+
} else {
|
|
256
|
+
maximumFittingFontSize = fontSize;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
context.font = `400 ${minimumFontSize}px "Dancing Script", cursive`;
|
|
261
|
+
|
|
262
|
+
const metrics = context.measureText(text);
|
|
263
|
+
const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
|
264
|
+
const x = (normalizedWidth.value - metrics.width) / 2;
|
|
265
|
+
const y = (normalizedHeight.value - textHeight) / 2 + metrics.actualBoundingBoxAscent;
|
|
266
|
+
|
|
267
|
+
context.save();
|
|
268
|
+
context.fillStyle = '#000000';
|
|
269
|
+
context.textBaseline = 'alphabetic';
|
|
270
|
+
context.fillText(text, x, y);
|
|
271
|
+
context.restore();
|
|
272
|
+
filled.value = true;
|
|
273
|
+
}
|
|
274
|
+
|
|
192
275
|
async function renderModelValue(modelValue: string | null | undefined) {
|
|
193
276
|
const requestId = ++loadRequestId;
|
|
194
277
|
errorMessage.value = null;
|
|
@@ -356,6 +439,7 @@ function startDrawing(event: PointerEvent) {
|
|
|
356
439
|
|
|
357
440
|
event.preventDefault();
|
|
358
441
|
loadRequestId += 1;
|
|
442
|
+
applyingTextSignature.value = false;
|
|
359
443
|
activePointerId = event.pointerId;
|
|
360
444
|
drawing = true;
|
|
361
445
|
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...",
|