generated from HugoRCD/default-repository
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add UI customization and refactor code
- Introduced a new app configuration file for defining UI colors - Refactored the main Vue component to use an array of fields, making it easier to add or remove fields in the future - Added more customization options for the signature preview, including font size, gap between elements, image size, text color, background color and alignment - Created new components for layout navigation bar and signature preview - Updated Nuxt configuration to include custom icon collections and added @nuxt/ui module - Removed unused rename.sh script from scripts directory - Added new types for form data and options related to signatures
- Loading branch information
Showing
9 changed files
with
233 additions
and
134 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export default defineAppConfig({ | ||
ui: { | ||
primary: 'cyan', | ||
gray: 'neutral', | ||
}, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,125 +1,118 @@ | ||
<script setup lang="ts"> | ||
const fullName = ref('') | ||
const jobTitle = ref('') | ||
const company = ref('') | ||
const email = ref('') | ||
const phone = ref('') | ||
const imagePreview = ref('') | ||
const copyButtonText = ref('Copy Signature') | ||
import type { Alignment, SignatureFormData, SignatureOptions } from '~~/types' | ||
const onFileChange = (e) => { | ||
const file = e.target.files[0] | ||
if (file) { | ||
if (file.type.startsWith('image/')) { | ||
const reader = new FileReader() | ||
reader.onload = (e) => { | ||
imagePreview.value = e.target.result | ||
} | ||
reader.readAsDataURL(file) | ||
} else { | ||
alert('Please select an image file') | ||
e.target.value = '' | ||
} | ||
} | ||
} | ||
const fields = [ | ||
{ name: 'image', label: 'Profile Picture', type: 'text' }, | ||
{ name: 'fullName', label: 'Full Name', type: 'text' }, | ||
{ name: 'jobTitle', label: 'Job Title', type: 'text' }, | ||
{ name: 'company', label: 'Company', type: 'text' }, | ||
{ name: 'email', label: 'Email', type: 'email' }, | ||
{ name: 'phone', label: 'Phone', type: 'tel' }, | ||
] | ||
const data = ref<SignatureFormData>({ | ||
image: 'https://avatars.githubusercontent.com/u/71938701?v=4', | ||
fullName: 'Hugo Richard', | ||
jobTitle: 'Developer', | ||
company: 'Apple', | ||
email: '[email protected]', | ||
phone: '0621562218', | ||
}) | ||
const generateSignatureHTML = computed(() => { | ||
return ` | ||
<table style="font-family: Arial, sans-serif; font-size: 14px; line-height: 1.4; color: #333333;"> | ||
<tr> | ||
<td style="vertical-align: top; padding-right: 15px;"> | ||
${imagePreview.value ? `<img src="${imagePreview.value}" alt="Profile Picture" style="width: 80px; height: 80px; border-radius: 50%; object-fit: cover;">` : ''} | ||
</td> | ||
<td style="vertical-align: top;"> | ||
<strong style="font-size: 16px; color: #000000;">${fullName.value}</strong><br> | ||
${jobTitle.value}<br> | ||
${company.value}<br> | ||
${email.value}<br> | ||
${phone.value} | ||
</td> | ||
</tr> | ||
</table> | ||
` | ||
const options = ref<SignatureOptions>({ | ||
fontSize: 14, | ||
gap: 10, | ||
imageSize: 80, | ||
textColor: '#000000', | ||
backgroundColor: '#ffffff', | ||
align: 'center', | ||
}) | ||
const copySignature = async () => { | ||
try { | ||
await navigator.clipboard.writeText(generateSignatureHTML.value) | ||
copyButtonText.value = 'Copied!' | ||
setTimeout(() => { | ||
copyButtonText.value = 'Copy Signature' | ||
}, 2000) | ||
} catch (err) { | ||
console.error('Failed to copy signature: ', err) | ||
copyButtonText.value = 'Failed to copy' | ||
} | ||
function setAlign(newAlign: Alignment) { | ||
options.value.align = newAlign | ||
} | ||
</script> | ||
|
||
<template> | ||
<div class="min-h-screen bg-gray-100 py-6 flex flex-col justify-center sm:py-12"> | ||
<div class="relative py-3 sm:max-w-xl sm:mx-auto"> | ||
<div class="relative px-4 py-10 bg-white shadow-lg sm:rounded-3xl sm:p-20"> | ||
<div class="max-w-md mx-auto"> | ||
<h1 class="text-2xl font-semibold mb-6 text-center">Email Signature Generator</h1> | ||
<div class="divide-y divide-gray-200"> | ||
<div class="py-8 text-base leading-6 space-y-4 text-gray-700 sm:text-lg sm:leading-7"> | ||
<div class="flex flex-col"> | ||
<label class="leading-loose">Profile Picture</label> | ||
<input type="file" @change="onFileChange" accept="image/*" class="px-4 py-2 border focus:ring-gray-500 focus:border-gray-900 w-full sm:text-sm border-gray-300 rounded-md focus:outline-none text-gray-600"> | ||
<img v-if="imagePreview" :src="imagePreview" alt="Profile Picture Preview" class="mt-2 w-24 h-24 object-cover rounded-full"> | ||
</div> | ||
<div class="flex flex-col"> | ||
<label class="leading-loose">Full Name</label> | ||
<input type="text" v-model="fullName" class="px-4 py-2 border focus:ring-gray-500 focus:border-gray-900 w-full sm:text-sm border-gray-300 rounded-md focus:outline-none text-gray-600" placeholder="John Doe"> | ||
</div> | ||
<div class="flex flex-col"> | ||
<label class="leading-loose">Job Title</label> | ||
<input type="text" v-model="jobTitle" class="px-4 py-2 border focus:ring-gray-500 focus:border-gray-900 w-full sm:text-sm border-gray-300 rounded-md focus:outline-none text-gray-600" placeholder="Software Engineer"> | ||
</div> | ||
<div class="flex flex-col"> | ||
<label class="leading-loose">Company</label> | ||
<input type="text" v-model="company" class="px-4 py-2 border focus:ring-gray-500 focus:border-gray-900 w-full sm:text-sm border-gray-300 rounded-md focus:outline-none text-gray-600" placeholder="Acme Inc."> | ||
</div> | ||
<div class="flex flex-col"> | ||
<label class="leading-loose">Email</label> | ||
<input type="email" v-model="email" class="px-4 py-2 border focus:ring-gray-500 focus:border-gray-900 w-full sm:text-sm border-gray-300 rounded-md focus:outline-none text-gray-600" placeholder="[email protected]"> | ||
</div> | ||
<div class="flex flex-col"> | ||
<label class="leading-loose">Phone</label> | ||
<input type="tel" v-model="phone" class="px-4 py-2 border focus:ring-gray-500 focus:border-gray-900 w-full sm:text-sm border-gray-300 rounded-md focus:outline-none text-gray-600" placeholder="+1 (555) 123-4567"> | ||
</div> | ||
<Html class="h-full"> | ||
<Body class="h-screen"> | ||
<LayoutNavbar /> | ||
<div class="flex justify-center bg-black text-white"> | ||
<div class="relative mx-auto flex-1 bg-gray-900 p-5 shadow-lg sm:max-w-xl"> | ||
<div class="space-y-6"> | ||
<div v-for="field in fields" :key="field.name"> | ||
<label :for="field.name" class="mb-2 block text-sm font-medium">{{ field.label }}</label> | ||
<UInput :id="field.name" v-model="data[field.name]" :type="field.type" /> | ||
</div> | ||
<div class="pt-4 text-base leading-6 font-bold sm:text-lg sm:leading-7"> | ||
<p class="text-gray-900">Signature Preview</p> | ||
<div class="mt-2 p-4 border rounded-md"> | ||
<div class="flex items-center space-x-4"> | ||
<img v-if="imagePreview" :src="imagePreview" alt="Profile Picture" class="w-16 h-16 object-cover rounded-full"> | ||
<div class="text-sm"> | ||
<p class="font-bold text-gray-900">{{ fullName }}</p> | ||
<p class="text-gray-600">{{ jobTitle }}</p> | ||
<p class="text-gray-600">{{ company }}</p> | ||
<p class="text-gray-600">{{ email }}</p> | ||
<p class="text-gray-600">{{ phone }}</p> | ||
</div> | ||
|
||
<div> | ||
<label class="mb-2 block text-sm font-medium">Customization</label> | ||
<div class="grid grid-cols-2 gap-4"> | ||
<div> | ||
<label for="fontSize" class="mb-1 block text-xs">Font Size</label> | ||
<URange | ||
id="fontSize" | ||
v-model="options.fontSize" | ||
type="range" | ||
:min="12" | ||
:max="24" | ||
/> | ||
</div> | ||
<div> | ||
<label for="gap" class="mb-1 block text-xs">Gap</label> | ||
<URange | ||
id="gap" | ||
v-model="options.gap" | ||
type="range" | ||
:min="0" | ||
:max="20" | ||
class="w-full" | ||
/> | ||
</div> | ||
<div> | ||
<label for="imageSize" class="mb-1 block text-xs">Image Size</label> | ||
<URange | ||
id="imageSize" | ||
v-model="options.imageSize" | ||
type="range" | ||
:min="40" | ||
:max="120" | ||
class="w-full" | ||
/> | ||
</div> | ||
<div> | ||
<label for="textColor" class="mb-1 block text-xs">Text Color</label> | ||
<UInput id="textColor" v-model="options.textColor" type="color" /> | ||
</div> | ||
<div class="flex flex-col"> | ||
<label for="backgroundColor" class="mb-1 block text-xs">Background Color</label> | ||
<UInput id="backgroundColor" v-model="options.backgroundColor" type="color" /> | ||
</div> | ||
<div> | ||
<label for="align" class="mb-1 block text-xs">Alignment</label> | ||
<UButtonGroup label="Alignment" class="w-full" orientation="horizontal"> | ||
<UButton | ||
v-for="alignment in ['top', 'center', 'bottom']" | ||
:key="alignment" | ||
:color="alignment === options.align ? 'primary' : 'gray'" | ||
@click="setAlign(alignment)" | ||
> | ||
{{ alignment }} | ||
</UButton> | ||
</UButtonGroup> | ||
</div> | ||
</div> | ||
<div class="mt-4"> | ||
<button | ||
@click="copySignature" | ||
class="w-full px-4 py-2 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75" | ||
:disabled="!fullName" | ||
:class="{ 'opacity-50 cursor-not-allowed': !fullName }" | ||
aria-live="polite" | ||
> | ||
{{ copyButtonText }} | ||
</button> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
<div class="flex flex-1 flex-col items-center justify-center"> | ||
<SignaturePreview | ||
:data | ||
:options | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
<UNotifications /> | ||
</Body> | ||
</Html> | ||
</template> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
<script setup lang="ts"> | ||
import type { Signature } from '~~/types' | ||
const { data, options } = defineProps<Signature>() | ||
const toast = useToast() | ||
const copyButtonText = ref('Copy Signature') | ||
const generateSignatureHTML = computed(() => { | ||
return ` | ||
<table style=" | ||
font-family: Arial, | ||
sans-serif; font-size: ${options.fontSize}px; | ||
line-height: 1.4; | ||
color: ${options.textColor}; | ||
background-color: ${options.backgroundColor}; | ||
width: 100%; | ||
"> | ||
<tr> | ||
<td style="vertical-align: top; padding-right: ${options.gap}px;"> | ||
${data.image ? `<img src="${data.image}" alt="Profile Picture" style="width: ${options.imageSize}px; height: ${options.imageSize}px; border-radius: 50%; object-fit: cover;">` : ''} | ||
</td> | ||
<td style="vertical-align: top;"> | ||
<strong style="font-size: ${options.fontSize + 2}px;">${data.fullName}</strong><br> | ||
${data.jobTitle}<br> | ||
${data.company}<br> | ||
${data.email}<br> | ||
${data.phone} | ||
</td> | ||
</tr> | ||
</table> | ||
` | ||
}) | ||
const copySignature = async () => { | ||
try { | ||
await navigator.clipboard.writeText(generateSignatureHTML.value) | ||
copyButtonText.value = 'Copied!' | ||
toast.add({ title: 'Copied!', description: 'Signature copied to clipboard', type: 'success', icon: 'lucide:clipboard-check' }) | ||
setTimeout(() => { | ||
copyButtonText.value = 'Copy Signature' | ||
}, 2000) | ||
} catch (err) { | ||
console.error('Failed to copy signature: ', err) | ||
copyButtonText.value = 'Failed to copy' | ||
} | ||
} | ||
</script> | ||
|
||
<template> | ||
<div class="flex w-full flex-col items-center justify-center space-y-4 px-4"> | ||
<div :style="{ color: options.textColor, backgroundColor: options.backgroundColor, width: `100%` }" class="rounded-md p-4"> | ||
<div class="flex" :style="{ gap: `${options.gap}px`, alignItems: options.align }"> | ||
<img :src="data.image" alt="Profile Picture" :style="{ width: `${options.imageSize}px`, height: `${options.imageSize}px` }" class="rounded-full object-cover"> | ||
<div :style="{ fontSize: `${options.fontSize}px` }"> | ||
<p class="font-bold"> | ||
{{ data.fullName }} | ||
</p> | ||
<p>{{ data.jobTitle }}</p> | ||
<p>{{ data.company }}</p> | ||
<p>{{ data.email }}</p> | ||
<p>{{ data.phone }}</p> | ||
</div> | ||
</div> | ||
</div> | ||
<UButton label="Copy Signature" color="primary" @click="copySignature" /> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<script setup lang="ts"> | ||
</script> | ||
|
||
<template> | ||
<div class="flex items-center justify-between bg-gray-900 px-4 py-2 shadow-lg"> | ||
<h1 class="font-instrument text-2xl font-bold text-white"> | ||
Inkly | ||
</h1> | ||
<UIcon name="lucide:github" class="text-white" /> | ||
</div> | ||
</template> | ||
|
||
<style scoped> | ||
@font-face { | ||
font-family: 'Instrument'; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.