130 lines
4.8 KiB
Vue
130 lines
4.8 KiB
Vue
<script setup lang="ts">
|
|
import { Head, useForm } from '@inertiajs/vue3';
|
|
import { Monitor, Moon, Save, Sun } from '@lucide/vue';
|
|
import type { AcceptableValue } from 'reka-ui';
|
|
import { toast } from 'vue-sonner';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import {
|
|
Field,
|
|
FieldError,
|
|
FieldGroup, FieldSet
|
|
} from '@/components/ui/field';
|
|
import { Label } from '@/components/ui/label';
|
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
|
import { applyAppearance } from '@/composables/useAppearance';
|
|
import { AppearanceMode as AppearanceModeConst } from '@/constants/appearance-mode';
|
|
import AccountLayout from '@/layouts/AccountLayout.vue';
|
|
import { formErrors } from '@/lib/form';
|
|
import { cn } from '@/lib/utils';
|
|
import type { AppearanceFormData, AppearanceMode } from '@/types/account';
|
|
|
|
import { update } from '@/routes/admin/account/appearance';
|
|
|
|
const props = defineProps<{
|
|
appearance: AppearanceMode;
|
|
}>();
|
|
|
|
const form = useForm<AppearanceFormData>({
|
|
appearance: props.appearance,
|
|
});
|
|
|
|
const options: Array<{
|
|
value: AppearanceMode;
|
|
label: string;
|
|
description: string;
|
|
icon: typeof Sun;
|
|
}> = [
|
|
{
|
|
value: AppearanceModeConst.LIGHT,
|
|
label: 'Terang',
|
|
description: 'Tampilan terang untuk lingkungan yang cukup pencahayaan.',
|
|
icon: Sun,
|
|
},
|
|
{
|
|
value: AppearanceModeConst.DARK,
|
|
label: 'Gelap',
|
|
description: 'Tampilan gelap yang nyaman di malam hari.',
|
|
icon: Moon,
|
|
},
|
|
{
|
|
value: AppearanceModeConst.SYSTEM,
|
|
label: 'Sistem',
|
|
description: 'Mengikuti pengaturan tampilan perangkat Anda.',
|
|
icon: Monitor,
|
|
},
|
|
];
|
|
|
|
function onAppearanceChange(value: AcceptableValue) {
|
|
if (typeof value !== 'string') {
|
|
return;
|
|
}
|
|
|
|
form.appearance = value as AppearanceMode;
|
|
applyAppearance(form.appearance);
|
|
}
|
|
|
|
function submit() {
|
|
form.put(update.url(), {
|
|
onError: (errors: any) => {
|
|
if (errors.system) {
|
|
toast.error(errors.system);
|
|
}
|
|
},
|
|
});
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
|
|
<Head title="Tampilan" />
|
|
|
|
<AccountLayout>
|
|
<form @submit.prevent="submit">
|
|
<Card>
|
|
<CardContent>
|
|
<FieldSet>
|
|
<FieldGroup>
|
|
<Field>
|
|
<RadioGroup :model-value="form.appearance" class="grid gap-3"
|
|
@update:model-value="onAppearanceChange">
|
|
<Label v-for="option in options" :key="option.value"
|
|
:for="`appearance-${option.value}`" :class="cn(
|
|
'flex cursor-pointer items-start gap-3 rounded-lg border p-4 transition-colors',
|
|
form.appearance === option.value
|
|
? 'border-primary bg-primary/5'
|
|
: 'hover:bg-accent/50',
|
|
)">
|
|
<RadioGroupItem :id="`appearance-${option.value}`" :value="option.value"
|
|
class="mt-0.5" />
|
|
<div class="flex flex-1 items-start gap-3">
|
|
<component :is="option.icon"
|
|
class="text-muted-foreground mt-0.5 size-5 shrink-0" />
|
|
<div class="space-y-1">
|
|
<p class="text-sm leading-none font-medium">
|
|
{{ option.label }}
|
|
</p>
|
|
<p class="text-muted-foreground text-sm">
|
|
{{ option.description }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</Label>
|
|
</RadioGroup>
|
|
<FieldError :errors="formErrors(form, 'appearance')" />
|
|
</Field>
|
|
</FieldGroup>
|
|
</FieldSet>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<div class="flex items-center justify-end gap-2 mt-6">
|
|
<Button type="submit" :disabled="form.processing">
|
|
<Save class="size-4" />
|
|
{{ form.processing ? 'Menyimpan...' : 'Simpan' }}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</AccountLayout>
|
|
</template>
|