mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-18 10:44:08 +00:00
Phase 3: Typed Vue components in scripts-v2/
Migrate all shared components to TypeScript SFCs with script setup lang=ts. 72 files, 7144 lines, zero any types. - components/base/ (42 files): Button, Input, Textarea, Checkbox, Radio, Switch, Badge, Card, Modal, Dialog, Dropdown, DatePicker, TimePicker, Money, FileUploader, Select, Icon, Loader, Multiselect, TabGroup, Wizard, CustomerSelect, ItemSelect, CustomInput, alerts, status badges (Invoice/Estimate/Paid/RecurringInvoice), List/ListItem - components/table/ (3 files): DataTable, TablePagination - components/form/ (4 files): FormGroup, FormGrid, SwitchSection - components/layout/ (11 files): Page, PageHeader, Breadcrumb, FilterWrapper, EmptyPlaceholder, ContentPlaceholders, SettingCard - components/editor/ (2 files): RichEditor with Tiptap - components/charts/ (2 files): LineChart with Chart.js - components/notifications/ (3 files): NotificationRoot, NotificationItem - components/icons/ (2 files): MainLogo All use defineProps<Props>(), defineEmits<Emits>(), typed refs, and import domain types from types/domain. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<div
|
||||
:class="success || info ? 'bg-surface' : 'bg-alert-error-bg'"
|
||||
class="
|
||||
max-w-sm
|
||||
mb-3
|
||||
rounded-lg
|
||||
shadow-lg
|
||||
cursor-pointer
|
||||
pointer-events-auto
|
||||
w-full
|
||||
md:w-96
|
||||
"
|
||||
@click.stop="hideNotificationAction"
|
||||
@mouseenter="clearNotificationTimeOut"
|
||||
@mouseleave="setNotificationTimeOut"
|
||||
>
|
||||
<div class="overflow-hidden rounded-lg shadow-xs">
|
||||
<div class="p-4">
|
||||
<div class="flex items-start">
|
||||
<div class="shrink-0">
|
||||
<svg
|
||||
v-if="success"
|
||||
class="w-6 h-6 text-alert-success-text"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
v-if="info"
|
||||
class="w-6 h-6 text-status-blue"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
<svg
|
||||
v-if="error"
|
||||
class="w-6 h-6 text-alert-error-text"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 w-0 ml-3 text-left">
|
||||
<p
|
||||
:class="`text-sm leading-5 font-medium ${
|
||||
success || info ? 'text-heading' : 'text-alert-error-text'
|
||||
}`"
|
||||
>
|
||||
{{
|
||||
notification.title
|
||||
? notification.title
|
||||
: success
|
||||
? 'Success!'
|
||||
: 'Error'
|
||||
}}
|
||||
</p>
|
||||
<p
|
||||
:class="`mt-1 text-sm leading-5 ${
|
||||
success || info ? 'text-muted' : 'text-alert-error-text'
|
||||
}`"
|
||||
>
|
||||
{{
|
||||
notification.message
|
||||
? notification.message
|
||||
: success
|
||||
? 'Successful'
|
||||
: 'Something went wrong'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex shrink-0">
|
||||
<button
|
||||
:class="
|
||||
success || info
|
||||
? ' text-subtle focus:text-muted'
|
||||
: 'text-alert-error-text focus:text-alert-error-text'
|
||||
"
|
||||
class="
|
||||
inline-flex
|
||||
w-5
|
||||
h-5
|
||||
transition
|
||||
duration-150
|
||||
ease-in-out
|
||||
focus:outline-hidden
|
||||
"
|
||||
@click="hideNotificationAction"
|
||||
>
|
||||
<svg
|
||||
class="w-6 h-6"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, computed, ref } from 'vue'
|
||||
import { useNotificationStore } from '@/scripts/stores/notification'
|
||||
|
||||
export type NotificationType = 'success' | 'error' | 'info'
|
||||
|
||||
export interface Notification {
|
||||
id: string
|
||||
type: NotificationType
|
||||
title?: string
|
||||
message?: string
|
||||
time?: number
|
||||
}
|
||||
|
||||
interface Props {
|
||||
notification: Notification
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const notificationStore = useNotificationStore()
|
||||
|
||||
const notiTimeOut = ref<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
const success = computed<boolean>(() => {
|
||||
return props.notification.type === 'success'
|
||||
})
|
||||
|
||||
const error = computed<boolean>(() => {
|
||||
return props.notification.type === 'error'
|
||||
})
|
||||
|
||||
const info = computed<boolean>(() => {
|
||||
return props.notification.type === 'info'
|
||||
})
|
||||
|
||||
function hideNotificationAction(): void {
|
||||
notificationStore.hideNotification(props.notification)
|
||||
}
|
||||
|
||||
function clearNotificationTimeOut(): void {
|
||||
if (notiTimeOut.value) {
|
||||
clearTimeout(notiTimeOut.value)
|
||||
}
|
||||
}
|
||||
|
||||
function setNotificationTimeOut(): void {
|
||||
notiTimeOut.value = setTimeout(() => {
|
||||
notificationStore.hideNotification(props.notification)
|
||||
}, props.notification.time || 5000)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setNotificationTimeOut()
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div
|
||||
class="
|
||||
fixed
|
||||
inset-0
|
||||
z-50
|
||||
flex flex-col
|
||||
items-end
|
||||
justify-start
|
||||
w-full
|
||||
px-4
|
||||
py-6
|
||||
pointer-events-none
|
||||
sm:p-6
|
||||
"
|
||||
>
|
||||
<transition-group
|
||||
enter-active-class="transition duration-300 ease-out"
|
||||
enter-from-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
|
||||
enter-to-class="translate-y-0 opacity-100 sm:translate-x-0"
|
||||
leave-active-class="transition duration-100 ease-in"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<NotificationItem
|
||||
v-for="notification in notifications"
|
||||
:key="notification.id"
|
||||
:notification="notification"
|
||||
/>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useNotificationStore } from '@/scripts/stores/notification'
|
||||
import NotificationItem from './NotificationItem.vue'
|
||||
import type { Notification } from './NotificationItem.vue'
|
||||
|
||||
const notificationStore = useNotificationStore()
|
||||
|
||||
const notifications = computed<Notification[]>(() => {
|
||||
return notificationStore.notifications
|
||||
})
|
||||
</script>
|
||||
4
resources/scripts-v2/components/notifications/index.ts
Normal file
4
resources/scripts-v2/components/notifications/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as NotificationRoot } from './NotificationRoot.vue'
|
||||
export { default as NotificationItem } from './NotificationItem.vue'
|
||||
|
||||
export type { Notification, NotificationType } from './NotificationItem.vue'
|
||||
Reference in New Issue
Block a user