refactor: replace PrimeVue components with custom App components for buttons, dialogs, and inputs
- Updated DangerZone.vue to use AppButton and AppDialog, replacing PrimeVue Button and Dialog components. - Refactored DomainsDns.vue to utilize AppButton, AppDialog, and AppInput, enhancing the UI consistency. - Modified NotificationSettings.vue and PlayerSettings.vue to implement AppButton and AppSwitch for better styling. - Replaced PrimeVue components in SecurityNConnected.vue with AppButton, AppDialog, and AppInput for a cohesive design. - Introduced AppConfirmHost for handling confirmation dialogs with a custom design. - Created AppToastHost for managing toast notifications with custom styling and behavior. - Added utility composables useAppConfirm and useAppToast for managing confirmation dialogs and toast notifications. - Implemented AppProgressBar and AppSwitch components for improved UI elements.
This commit is contained in:
91
src/components/app/AppInput.vue
Normal file
91
src/components/app/AppInput.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { computed } from 'vue';
|
||||
|
||||
// Vue macro is available at compile time; provide a safe fallback for typecheck.
|
||||
declare const defineModelModifiers: undefined | (<T>() => T);
|
||||
|
||||
|
||||
type Props = {
|
||||
modelValue?: string | number | null;
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
readonly?: boolean;
|
||||
disabled?: boolean;
|
||||
id?: string;
|
||||
name?: string;
|
||||
autocomplete?: string;
|
||||
inputClass?: string;
|
||||
wrapperClass?: string;
|
||||
min?: number | string;
|
||||
max?: number | string;
|
||||
step?: number | string;
|
||||
maxlength?: number;
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: '',
|
||||
type: 'text',
|
||||
placeholder: '',
|
||||
readonly: false,
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: string | number | null): void;
|
||||
(e: 'enter'): void;
|
||||
}>();
|
||||
|
||||
const modelModifiers = (typeof defineModelModifiers === 'function'
|
||||
? defineModelModifiers<{ number?: boolean }>()
|
||||
: ({} as { number?: boolean }));
|
||||
|
||||
const isNumberLike = computed(() => props.type === 'number' || !!modelModifiers.number);
|
||||
|
||||
const onInput = (e: Event) => {
|
||||
const el = e.target as HTMLInputElement;
|
||||
const raw = el.value;
|
||||
if (isNumberLike.value) {
|
||||
if (raw === '') {
|
||||
emit('update:modelValue', null);
|
||||
return;
|
||||
}
|
||||
const n = Number(raw);
|
||||
emit('update:modelValue', Number.isNaN(n) ? null : n);
|
||||
return;
|
||||
}
|
||||
emit('update:modelValue', raw);
|
||||
};
|
||||
|
||||
const onKeyup = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter') emit('enter');
|
||||
};
|
||||
|
||||
const baseInputClass = 'w-full px-3 py-2 rounded-md border border-border bg-surface text-foreground placeholder:text-foreground/40 focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary/50 disabled:opacity-60 disabled:cursor-not-allowed';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('relative', wrapperClass)">
|
||||
<div v-if="$slots.prefix" class="absolute left-3 top-1/2 -translate-y-1/2 text-foreground/50">
|
||||
<slot name="prefix" />
|
||||
</div>
|
||||
|
||||
<input
|
||||
:id="id"
|
||||
:name="name"
|
||||
:type="type"
|
||||
:value="modelValue ?? ''"
|
||||
:placeholder="placeholder"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:autocomplete="autocomplete"
|
||||
:min="min"
|
||||
:max="max"
|
||||
:step="step"
|
||||
:maxlength="maxlength"
|
||||
:class="cn(baseInputClass, $slots.prefix ? 'pl-10' : '', inputClass)"
|
||||
@input="onInput"
|
||||
@keyup="onKeyup"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user