Skip to content

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>

Released under the MIT License.