Settings Layout
A comprehensive settings page pattern featuring categorized sections, vertical navigation, and various form controls.
Demo
Implementation
vue
<script setup lang="ts">
import { ref } from 'vue';
// 1. Component Imports (Default)
import CLCard from '@codeandfunction/callaloo/CLCard';
import CLNavSection from '@codeandfunction/callaloo/CLNavSection';
import CLInput from '@codeandfunction/callaloo/CLInput';
import CLSelect from '@codeandfunction/callaloo/CLSelect';
import CLCheckbox from '@codeandfunction/callaloo/CLCheckbox';
import CLButton from '@codeandfunction/callaloo/CLButton';
import CLHeading from '@codeandfunction/callaloo/CLHeading';
import CLText from '@codeandfunction/callaloo/CLText';
import { useToast } from '@codeandfunction/callaloo/composables/useToast';
import type { CLNavItem, CLOption } from '@codeandfunction/callaloo';
// 2. Enum & Type Imports (Named)
import {
CLAlign,
CLButtonTypes,
CLColors,
CLColorVariants,
CLHeadingLevels,
CLHeadingTypes,
CLOrientation,
CLTextTypes,
} from '@codeandfunction/callaloo';
// 3. State & Logic
const activeCategory = ref('profile');
const toast = useToast();
const categories: CLNavItem[] = [
{ id: 'profile', label: 'Profile', onClick: () => activeCategory.value = 'profile' },
{ id: 'account', label: 'Account', onClick: () => activeCategory.value = 'account' },
{ id: 'notifications', label: 'Notifications', onClick: () => activeCategory.value = 'notifications' },
{ id: 'privacy', label: 'Privacy & Security', onClick: () => activeCategory.value = 'privacy' },
];
interface ProfileData {
email: string;
fullName: string;
marketingEmails: boolean;
timezone: string;
}
const profileData = ref<ProfileData>({
email: '[email protected]',
fullName: 'John Doe',
marketingEmails: true,
timezone: 'UTC-5',
});
const timezoneOptions: CLOption[] = [
{ label: 'UTC-8 (Pacific Time)', value: 'UTC-8' },
{ label: 'UTC-5 (Eastern Time)', value: 'UTC-5' },
{ label: 'UTC+0 (Greenwich Mean Time)', value: 'UTC+0' },
{ label: 'UTC+1 (Central European Time)', value: 'UTC+1' },
];
const handleSave = (): void => {
toast.showToast({
color: CLColors.Success,
message: 'Settings saved!',
title: 'Success',
});
};
</script>
<template>
<div class="pattern-preview">
<div class="settings-container">
<div class="settings-layout">
<aside class="settings-sidebar">
<CLHeading :level="CLHeadingLevels.H3">Settings</CLHeading>
<CLNavSection
:nav-items="categories"
:type="CLOrientation.Vertical"
:color="CLColors.Primary"
/>
</aside>
<main class="settings-main">
<CLCard :variant="CLColorVariants.Ghost" v-if="activeCategory === 'profile'" elevated>
<template #heading>
<div class="card-header-content">
<CLHeading :level="CLHeadingLevels.H4" :type="CLHeadingTypes.XXXL">Profile Information</CLHeading>
<CLText :type="CLTextTypes.Small" :color="CLColors.Neutral">Update your account's profile information and email address.</CLText>
</div>
</template>
<form @submit.prevent="handleSave" class="form-grid">
<CLInput
id="full-name"
name="full-name"
label="Full Name"
v-model="profileData.fullName"
fluid
/>
<CLInput
id="email"
name="email"
label="Email Address"
v-model="profileData.email"
fluid
/>
<CLSelect
id="timezone"
name="timezone"
label="Timezone"
v-model="profileData.timezone"
:options="timezoneOptions"
fluid
/>
<div class="terms-check">
<CLCheckbox
v-model="profileData.marketingEmails"
id="marketing"
name="marketing"
/>
<label for="marketing" class="terms-label">
<CLText :type="CLTextTypes.Small" as="span">
Receive marketing emails and updates
</CLText>
</label>
</div>
<div class="form-actions">
<CLButton
:type="CLButtonTypes.Submit"
:color="CLColors.Primary"
>
Save Changes
</CLButton>
</div>
</form>
</CLCard>
<CLCard :variant="CLColorVariants.Ghost" v-else elevated>
<template #heading>
<CLHeading :level="CLHeadingLevels.H4" :type="CLHeadingTypes.XXXL">
{{ categories.find(c => c.id === activeCategory)?.label }}
</CLHeading>
</template>
<div class="placeholder-section">
<CLText :align="CLAlign.Center">This section is currently under development.</CLText>
</div>
</CLCard>
</main>
</div>
</div>
</div>
</template>
<style scoped>
.settings-container {
display: flex;
flex-direction: column;
gap: 2rem;
}
.settings-layout {
display: flex;
flex-direction: column;
gap: 3rem;
}
@media (min-width: 768px) {
.settings-layout {
display: grid;
grid-template-columns: 240px 1fr;
gap: 3rem;
}
}
.settings-sidebar {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.settings-main {
display: flex;
flex-direction: column;
gap: 2rem;
}
.card-header-content {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.form-grid {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.terms-check {
display: flex;
align-items: center;
gap: 0.5rem;
}
.terms-label {
cursor: pointer;
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 1rem;
margin-top: 1rem;
}
.placeholder-section {
padding: 2rem;
text-align: center;
}
</style>