store/resources/js/pages/admin/account/Appearance.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>