Vue Material Component Framework implementing Google's Material Design specification with comprehensive UI components, theming system, and accessibility features.
—
Reactive composition functions for accessing Vuetify's framework features including theming, display management, internationalization, and utility functions.
Composable for accessing and managing the application's theme system including colors, variants, and theme switching.
/**
* Theme management composable
* @returns Theme instance with reactive theme state and controls
*/
function useTheme(): ThemeInstance;
interface ThemeInstance {
/** Currently active theme name */
current: Ref<ThemeDefinition>;
/** Available themes */
themes: Ref<Record<string, ThemeDefinition>>;
/** Whether current theme is dark */
isDisabled: Ref<boolean>;
/** Global theme name */
name: Ref<string>;
/** Theme computed styles */
computedThemes: ComputedRef<Record<string, ThemeDefinition>>;
/** Theme CSS variables */
themeClasses: Ref<Record<string, boolean>>;
/** Theme CSS variables as styles */
styles: ComputedRef<string>;
}
interface ThemeDefinition {
/** Whether theme is dark mode */
dark: boolean;
/** Theme colors palette */
colors: Record<string, string>;
/** CSS custom properties */
variables: Record<string, string | number>;
}Usage Examples:
<template>
<div>
<!-- Theme toggle -->
<v-btn @click="toggleTheme" :color="theme.current.value.dark ? 'white' : 'black'">
{{ theme.current.value.dark ? 'Light Mode' : 'Dark Mode' }}
</v-btn>
<!-- Theme-aware styling -->
<v-card :color="theme.current.value.colors.surface">
<v-card-text :style="{ color: theme.current.value.colors.onSurface }">
Current theme: {{ theme.name.value }}
</v-card-text>
</v-card>
<!-- Custom theme switcher -->
<v-select
v-model="theme.name.value"
:items="themeOptions"
label="Select Theme"
/>
<!-- Access theme colors -->
<div class="color-palette">
<div
v-for="(color, name) in theme.current.value.colors"
:key="name"
:style="{ backgroundColor: color }"
class="color-swatch"
:title="`${name}: ${color}`"
>
{{ name }}
</div>
</div>
</div>
</template>
<script setup>
const theme = useTheme();
const themeOptions = computed(() =>
Object.keys(theme.themes.value).map(name => ({
title: name.charAt(0).toUpperCase() + name.slice(1),
value: name
}))
);
const toggleTheme = () => {
theme.name.value = theme.current.value.dark ? 'light' : 'dark';
};
// Create custom theme
const createCustomTheme = () => {
theme.themes.value.custom = {
dark: false,
colors: {
primary: '#6200EA',
secondary: '#03DAC6',
surface: '#F5F5F5',
onSurface: '#1C1B1F'
},
variables: {
'border-radius-root': '8px'
}
};
};
// Watch theme changes
watch(() => theme.current.value.dark, (isDark) => {
document.documentElement.style.colorScheme = isDark ? 'dark' : 'light';
});
</script>
<style>
.color-palette {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 8px;
margin-top: 16px;
}
.color-swatch {
padding: 12px;
border-radius: 4px;
font-size: 12px;
color: white;
text-shadow: 1px 1px 2px rgba(0,0,0,0.7);
}
</style>Composable for responsive design and display utilities including breakpoints, screen size detection, and mobile/desktop detection.
/**
* Display and responsive design composable
* @returns Display instance with reactive breakpoint and size information
*/
function useDisplay(): DisplayInstance;
interface DisplayInstance {
/** Current display breakpoints */
xs: Ref<boolean>;
sm: Ref<boolean>;
md: Ref<boolean>;
lg: Ref<boolean>;
xl: Ref<boolean>;
xxl: Ref<boolean>;
/** Breakpoint names when active */
smAndUp: Ref<boolean>;
mdAndUp: Ref<boolean>;
lgAndUp: Ref<boolean>;
xlAndUp: Ref<boolean>;
smAndDown: Ref<boolean>;
mdAndDown: Ref<boolean>;
lgAndDown: Ref<boolean>;
xlAndDown: Ref<boolean>;
/** Current breakpoint name */
name: Ref<string>;
/** Display dimensions */
height: Ref<number>;
width: Ref<number>;
/** Platform detection */
mobile: Ref<boolean>;
mobileBreakpoint: Ref<number | string>;
/** Threshold values */
thresholds: Ref<DisplayThresholds>;
/** Platform type */
platform: Ref<'android' | 'ios' | 'desktop'>;
/** Touch capability */
touch: Ref<boolean>;
/** Screen pixel density */
ssr: boolean;
}
interface DisplayThresholds {
xs: number;
sm: number;
md: number;
lg: number;
xl: number;
xxl: number;
}
interface DisplayBreakpoint {
xs: boolean;
sm: boolean;
md: boolean;
lg: boolean;
xl: boolean;
xxl: boolean;
}Usage Examples:
<template>
<div>
<!-- Responsive layout -->
<v-container>
<v-row>
<v-col
:cols="display.xs.value ? 12 : display.sm.value ? 6 : 4"
v-for="item in items"
:key="item.id"
>
<v-card>{{ item.title }}</v-card>
</v-col>
</v-row>
</v-container>
<!-- Conditional rendering based on screen size -->
<v-navigation-drawer v-if="display.mdAndUp.value" permanent>
Desktop navigation
</v-navigation-drawer>
<v-bottom-navigation v-if="display.mobile.value">
Mobile navigation
</v-bottom-navigation>
<!-- Display information -->
<v-card>
<v-card-title>Display Information</v-card-title>
<v-card-text>
<p>Breakpoint: {{ display.name.value }}</p>
<p>Size: {{ display.width.value }}x{{ display.height.value }}</p>
<p>Mobile: {{ display.mobile.value }}</p>
<p>Platform: {{ display.platform.value }}</p>
<p>Touch: {{ display.touch.value }}</p>
<div class="breakpoints">
<v-chip
v-for="breakpoint in breakpoints"
:key="breakpoint"
:color="display[breakpoint].value ? 'primary' : 'default'"
:variant="display[breakpoint].value ? 'flat' : 'outlined'"
>
{{ breakpoint.toUpperCase() }}
</v-chip>
</div>
</v-card-text>
</v-card>
<!-- Responsive component props -->
<v-btn
:size="display.xs.value ? 'small' : 'large'"
:variant="display.mobile.value ? 'outlined' : 'elevated'"
:block="display.smAndDown.value"
>
Responsive Button
</v-btn>
<!-- Responsive images -->
<v-img
:src="getResponsiveImageSrc()"
:aspect-ratio="display.xs.value ? 1 : 16/9"
:height="display.mobile.value ? 200 : 400"
/>
</div>
</template>
<script setup>
const display = useDisplay();
const breakpoints = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
const items = ref([
{ id: 1, title: 'Item 1' },
{ id: 2, title: 'Item 2' },
{ id: 3, title: 'Item 3' },
{ id: 4, title: 'Item 4' },
]);
const getResponsiveImageSrc = () => {
if (display.xs.value) return '/images/small.jpg';
if (display.sm.value) return '/images/medium.jpg';
return '/images/large.jpg';
};
// Computed properties for complex responsive logic
const columns = computed(() => {
if (display.xs.value) return 1;
if (display.sm.value) return 2;
if (display.md.value) return 3;
return 4;
});
const sidebarVisible = computed(() => {
return display.lgAndUp.value && !display.mobile.value;
});
// Watch breakpoint changes
watch(() => display.name.value, (newBreakpoint, oldBreakpoint) => {
console.log(`Breakpoint changed from ${oldBreakpoint} to ${newBreakpoint}`);
});
// Responsive data fetching
watchEffect(() => {
if (display.mobile.value) {
// Load mobile-optimized data
loadMobileData();
} else {
// Load desktop data with more details
loadDesktopData();
}
});
</script>
<style>
.breakpoints {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 16px;
}
</style>Composable for internationalization, managing locale settings, translations, and text formatting.
/**
* Internationalization composable
* @returns Locale instance for managing translations and locale settings
*/
function useLocale(): LocaleInstance;
interface LocaleInstance {
/** Current locale code */
current: Ref<string>;
/** Fallback locale code */
fallback: Ref<string>;
/** Available locales */
locales: Ref<Record<string, LocaleMessages>>;
/** Translation messages */
messages: Ref<Record<string, LocaleMessages>>;
/** Translate function */
t: (key: string, ...params: unknown[]) => string;
/** Number formatting function */
n: (value: number) => string;
/** Provide locale to components */
provide: Record<string, any>;
/** RTL (right-to-left) state */
isRtl: Ref<boolean>;
}
interface LocaleMessages {
[key: string]: string | LocaleMessages;
}
interface LocaleOptions {
/** Default locale */
locale?: string;
/** Fallback locale */
fallback?: string;
/** Translation messages */
messages?: Record<string, LocaleMessages>;
/** RTL locale configuration */
rtl?: Record<string, boolean>;
}Usage Examples:
<template>
<div>
<!-- Language selector -->
<v-select
v-model="locale.current.value"
:items="availableLocales"
:label="locale.t('selectLanguage')"
prepend-icon="mdi-translate"
/>
<!-- Translated content -->
<v-card>
<v-card-title>{{ locale.t('welcome') }}</v-card-title>
<v-card-text>
<p>{{ locale.t('description') }}</p>
<p>{{ locale.t('userCount', userCount) }}</p>
<p>{{ locale.t('lastLogin', formattedDate) }}</p>
</v-card-text>
</v-card>
<!-- Formatted numbers -->
<v-list>
<v-list-item>
<v-list-item-title>{{ locale.t('price') }}</v-list-item-title>
<v-list-item-subtitle>{{ locale.n(1234.56) }}</v-list-item-subtitle>
</v-list-item>
<v-list-item>
<v-list-item-title>{{ locale.t('quantity') }}</v-list-item-title>
<v-list-item-subtitle>{{ locale.n(42) }}</v-list-item-subtitle>
</v-list-item>
</v-list>
<!-- RTL awareness -->
<div :class="{ 'text-right': locale.isRtl.value }">
{{ locale.t('directionalText') }}
</div>
<!-- Conditional content based on locale -->
<v-alert
v-if="locale.current.value === 'ar'"
type="info"
>
{{ locale.t('arabicSpecificMessage') }}
</v-alert>
</div>
</template>
<script setup>
const locale = useLocale();
const availableLocales = computed(() => [
{ title: 'English', value: 'en' },
{ title: 'Español', value: 'es' },
{ title: 'Français', value: 'fr' },
{ title: 'العربية', value: 'ar' },
{ title: '中文', value: 'zh' },
]);
const userCount = ref(1337);
const formattedDate = computed(() => {
return new Date().toLocaleDateString(locale.current.value);
});
// Add custom translations
const addTranslations = () => {
locale.messages.value = {
...locale.messages.value,
en: {
welcome: 'Welcome',
description: 'This is a multilingual application',
userCount: 'We have {0} users',
lastLogin: 'Last login: {0}',
selectLanguage: 'Select Language',
price: 'Price',
quantity: 'Quantity',
directionalText: 'This text respects text direction',
...locale.messages.value.en
},
es: {
welcome: 'Bienvenido',
description: 'Esta es una aplicación multiidioma',
userCount: 'Tenemos {0} usuarios',
lastLogin: 'Último inicio de sesión: {0}',
selectLanguage: 'Seleccionar Idioma',
price: 'Precio',
quantity: 'Cantidad',
directionalText: 'Este texto respeta la dirección del texto',
},
fr: {
welcome: 'Bienvenue',
description: 'Ceci est une application multilingue',
userCount: 'Nous avons {0} utilisateurs',
lastLogin: 'Dernière connexion : {0}',
selectLanguage: 'Choisir la Langue',
price: 'Prix',
quantity: 'Quantité',
directionalText: 'Ce texte respecte la direction du texte',
},
ar: {
welcome: 'مرحبا',
description: 'هذا تطبيق متعدد اللغات',
userCount: 'لدينا {0} مستخدم',
lastLogin: 'آخر تسجيل دخول: {0}',
selectLanguage: 'اختر اللغة',
price: 'السعر',
quantity: 'الكمية',
directionalText: 'هذا النص يحترم اتجاه النص',
arabicSpecificMessage: 'رسالة خاصة باللغة العربية'
}
};
};
// Dynamic translations loading
const loadLocaleMessages = async (localeCode) => {
try {
const messages = await import(`../locales/${localeCode}.js`);
locale.messages.value[localeCode] = messages.default;
} catch (error) {
console.warn(`Failed to load locale: ${localeCode}`);
}
};
// Watch locale changes
watch(() => locale.current.value, (newLocale) => {
// Update document language
document.documentElement.lang = newLocale;
// Load locale-specific data
loadLocaleMessages(newLocale);
});
onMounted(() => {
addTranslations();
});
</script>Composable for right-to-left text direction support and layout management.
/**
* Right-to-left text direction composable
* @returns RTL instance for managing text direction
*/
function useRtl(): RtlInstance;
interface RtlInstance {
/** Whether current locale uses RTL */
isRtl: Ref<boolean>;
/** RTL value as computed property */
rtl: Ref<boolean>;
/** RTL class name */
rtlClasses: Ref<string>;
}
interface RtlOptions {
/** RTL configuration per locale */
rtl?: Record<string, boolean>;
}Usage Examples:
<template>
<div :dir="rtl.isRtl.value ? 'rtl' : 'ltr'">
<!-- RTL-aware layout -->
<v-app-bar :class="rtl.rtlClasses.value">
<v-btn
:icon="rtl.isRtl.value ? 'mdi-menu-right' : 'mdi-menu'"
@click="toggleDrawer"
/>
<v-toolbar-title>{{ title }}</v-toolbar-title>
<v-spacer />
<v-btn @click="toggleRtl">
{{ rtl.isRtl.value ? 'LTR' : 'RTL' }}
</v-btn>
</v-app-bar>
<!-- Navigation drawer with RTL awareness -->
<v-navigation-drawer
v-model="drawer"
:location="rtl.isRtl.value ? 'right' : 'left'"
>
<v-list>
<v-list-item
v-for="item in navigationItems"
:key="item.id"
:prepend-icon="item.icon"
:title="item.title"
@click="navigateTo(item.route)"
/>
</v-list>
</v-navigation-drawer>
<!-- Content with RTL styling -->
<v-main>
<v-container>
<!-- Text alignment -->
<v-card :class="{ 'text-right': rtl.isRtl.value }">
<v-card-title>{{ cardTitle }}</v-card-title>
<v-card-text>
<p>{{ longText }}</p>
</v-card-text>
</v-card>
<!-- Form with RTL layout -->
<v-form class="mt-4">
<v-text-field
v-model="name"
:label="nameLabel"
:prepend-icon="rtl.isRtl.value ? 'mdi-account-outline' : undefined"
:append-icon="rtl.isRtl.value ? undefined : 'mdi-account-outline'"
/>
<v-textarea
v-model="message"
:label="messageLabel"
:style="{ textAlign: rtl.isRtl.value ? 'right' : 'left' }"
/>
</v-form>
<!-- Chips with RTL spacing -->
<div :class="['d-flex', 'flex-wrap', rtl.isRtl.value ? 'justify-end' : 'justify-start']">
<v-chip
v-for="tag in tags"
:key="tag"
:class="rtl.isRtl.value ? 'ml-2 mb-2' : 'mr-2 mb-2'"
>
{{ tag }}
</v-chip>
</div>
</v-container>
</v-main>
</div>
</template>
<script setup>
const rtl = useRtl();
const locale = useLocale();
const drawer = ref(false);
const name = ref('');
const message = ref('');
const title = computed(() =>
rtl.isRtl.value ? 'تطبيق ويب' : 'Web Application'
);
const cardTitle = computed(() =>
rtl.isRtl.value ? 'المحتوى' : 'Content'
);
const nameLabel = computed(() =>
rtl.isRtl.value ? 'الاسم' : 'Name'
);
const messageLabel = computed(() =>
rtl.isRtl.value ? 'الرسالة' : 'Message'
);
const longText = computed(() =>
rtl.isRtl.value
? 'هذا نص طويل لتوضيح كيفية تعامل التطبيق مع النصوص باللغة العربية والتي تُكتب من اليمين إلى اليسار.'
: 'This is a long text to demonstrate how the application handles right-to-left text direction and layout adjustments.'
);
const navigationItems = computed(() => [
{
id: 1,
title: rtl.isRtl.value ? 'الرئيسية' : 'Home',
icon: 'mdi-home',
route: '/'
},
{
id: 2,
title: rtl.isRtl.value ? 'حول' : 'About',
icon: 'mdi-information',
route: '/about'
},
{
id: 3,
title: rtl.isRtl.value ? 'اتصال' : 'Contact',
icon: 'mdi-phone',
route: '/contact'
}
]);
const tags = computed(() =>
rtl.isRtl.value
? ['تقنية', 'ويب', 'تطوير']
: ['Technology', 'Web', 'Development']
);
const toggleDrawer = () => {
drawer.value = !drawer.value;
};
const toggleRtl = () => {
// Toggle between RTL and LTR for demonstration
locale.current.value = rtl.isRtl.value ? 'en' : 'ar';
};
const navigateTo = (route) => {
console.log(`Navigating to: ${route}`);
drawer.value = false;
};
// Watch RTL changes for additional setup
watch(() => rtl.isRtl.value, (isRtl) => {
// Update document direction
document.documentElement.dir = isRtl ? 'rtl' : 'ltr';
// Apply global RTL styles
document.body.classList.toggle('v-locale--is-rtl', isRtl);
// Close drawer on direction change to prevent layout issues
drawer.value = false;
});
</script>
<style>
.v-locale--is-rtl .v-navigation-drawer--left {
right: 0;
left: auto;
}
.v-locale--is-rtl .v-list-item__prepend {
margin-inline-start: 0;
margin-inline-end: 16px;
}
</style>Composable for managing component default props and global configuration.
/**
* Component defaults management composable
* @returns Defaults instance for managing component defaults
*/
function useDefaults(): DefaultsInstance;
interface DefaultsInstance {
/** Get component defaults */
getDefaults(): Record<string, any>;
/** Reset to initial defaults */
reset(): void;
/** Set global defaults for components */
global: Record<string, any>;
}
interface DefaultsOptions {
/** Default props for components */
[componentName: string]: Record<string, any>;
}Usage Examples:
<template>
<div>
<!-- Components using default props -->
<v-btn>Default Button</v-btn>
<v-card>
<v-card-title>Default Card</v-card-title>
</v-card>
<!-- Override defaults with explicit props -->
<v-btn color="success" variant="outlined">
Custom Button
</v-btn>
<!-- Form with default styling -->
<v-form>
<v-text-field label="Name" />
<v-text-field label="Email" type="email" />
<v-select :items="options" label="Options" />
</v-form>
</div>
</template>
<script setup>
const defaults = useDefaults();
// Set global defaults for components
const setGlobalDefaults = () => {
defaults.global = {
VBtn: {
color: 'primary',
variant: 'elevated',
size: 'default'
},
VCard: {
variant: 'outlined',
elevation: 2
},
VTextField: {
variant: 'outlined',
density: 'compact'
},
VSelect: {
variant: 'outlined',
density: 'compact'
}
};
};
// Get current defaults
const currentDefaults = computed(() => defaults.getDefaults());
// Reset defaults
const resetDefaults = () => {
defaults.reset();
};
const options = ref([
{ title: 'Option 1', value: 1 },
{ title: 'Option 2', value: 2 }
]);
onMounted(() => {
setGlobalDefaults();
});
</script>Composable for date handling, formatting, and localization with different date adapter support.
/**
* Date handling and formatting composable
* @returns Date instance for date operations and formatting
*/
function useDate(): DateInstance;
interface DateInstance {
/** Date adapter instance */
adapter: DateAdapter;
/** Current locale */
locale: Ref<string>;
/** Format date to string */
format: (date: any, formatString: string) => string;
/** Parse string to date */
parse: (value: string, format: string) => any;
/** Add time to date */
addDays: (date: any, days: number) => any;
addMonths: (date: any, months: number) => any;
addYears: (date: any, years: number) => any;
/** Date calculations */
startOfDay: (date: any) => any;
endOfDay: (date: any) => any;
startOfMonth: (date: any) => any;
endOfMonth: (date: any) => any;
/** Date comparisons */
isEqual: (date1: any, date2: any) => boolean;
isBefore: (date1: any, date2: any) => boolean;
isAfter: (date1: any, date2: any) => boolean;
isSameDay: (date1: any, date2: any) => boolean;
isSameMonth: (date1: any, date2: any) => boolean;
/** Date properties */
getYear: (date: any) => number;
getMonth: (date: any) => number;
getDate: (date: any) => number;
getDayOfWeek: (date: any) => number;
/** Validation */
isValid: (date: any) => boolean;
}
interface DateAdapter {
date(value?: any): any;
format(date: any, formatString: string): string;
parseISO(date: string): any;
toISO(date: any): string;
}
interface DateOptions {
/** Date adapter to use */
adapter?: DateAdapter;
/** Locale for date formatting */
locale?: string;
/** Date formats */
formats?: Record<string, string>;
}Usage Examples:
<template>
<div>
<!-- Date display -->
<v-card>
<v-card-title>Date Information</v-card-title>
<v-card-text>
<p>Today: {{ date.format(today, 'fullDate') }}</p>
<p>Short: {{ date.format(today, 'short') }}</p>
<p>ISO: {{ date.format(today, 'YYYY-MM-DD') }}</p>
<p>Custom: {{ date.format(today, 'MMMM Do, YYYY') }}</p>
</v-card-text>
</v-card>
<!-- Date picker with formatting -->
<v-date-picker
v-model="selectedDate"
:format="dateFormat"
:locale="date.locale.value"
/>
<!-- Date operations -->
<v-list>
<v-list-item>
<v-list-item-title>Start of Month</v-list-item-title>
<v-list-item-subtitle>{{ date.format(startOfMonth, 'short') }}</v-list-item-subtitle>
</v-list-item>
<v-list-item>
<v-list-item-title>End of Month</v-list-item-title>
<v-list-item-subtitle>{{ date.format(endOfMonth, 'short') }}</v-list-item-subtitle>
</v-list-item>
<v-list-item>
<v-list-item-title>Next Week</v-list-item-title>
<v-list-item-subtitle>{{ date.format(nextWeek, 'short') }}</v-list-item-subtitle>
</v-list-item>
</v-list>
<!-- Date range picker -->
<v-form>
<v-text-field
v-model="startDateString"
label="Start Date"
type="date"
@update:model-value="validateDateRange"
/>
<v-text-field
v-model="endDateString"
label="End Date"
type="date"
:error="dateRangeError"
:error-messages="dateRangeError ? 'End date must be after start date' : ''"
@update:model-value="validateDateRange"
/>
</v-form>
<!-- Relative time display -->
<v-chip-group>
<v-chip
v-for="event in upcomingEvents"
:key="event.id"
:color="getEventColor(event.date)"
>
{{ event.title }} - {{ getRelativeTime(event.date) }}
</v-chip>
</v-chip-group>
</div>
</template>
<script setup>
const date = useDate();
const today = ref(date.adapter.date());
const selectedDate = ref(date.adapter.date());
const startDateString = ref('');
const endDateString = ref('');
const dateRangeError = ref(false);
const dateFormat = computed(() => {
return date.locale.value === 'en' ? 'MM/DD/YYYY' : 'DD/MM/YYYY';
});
const startOfMonth = computed(() => date.startOfMonth(today.value));
const endOfMonth = computed(() => date.endOfMonth(today.value));
const nextWeek = computed(() => date.addDays(today.value, 7));
const upcomingEvents = ref([
{ id: 1, title: 'Meeting', date: date.addDays(today.value, 1) },
{ id: 2, title: 'Deadline', date: date.addDays(today.value, 5) },
{ id: 3, title: 'Conference', date: date.addDays(today.value, 14) },
]);
const validateDateRange = () => {
if (startDateString.value && endDateString.value) {
const startDate = date.parse(startDateString.value, 'YYYY-MM-DD');
const endDate = date.parse(endDateString.value, 'YYYY-MM-DD');
dateRangeError.value = date.isAfter(startDate, endDate);
} else {
dateRangeError.value = false;
}
};
const getRelativeTime = (eventDate) => {
const diffDays = Math.ceil((eventDate - today.value) / (1000 * 60 * 60 * 24));
if (diffDays === 0) return 'Today';
if (diffDays === 1) return 'Tomorrow';
if (diffDays < 7) return `In ${diffDays} days`;
if (diffDays < 30) return `In ${Math.ceil(diffDays / 7)} weeks`;
return `In ${Math.ceil(diffDays / 30)} months`;
};
const getEventColor = (eventDate) => {
const diffDays = Math.ceil((eventDate - today.value) / (1000 * 60 * 60 * 24));
if (diffDays <= 1) return 'error';
if (diffDays <= 7) return 'warning';
return 'success';
};
// Custom date formatting
const formatCustomDate = (dateValue, format) => {
if (!date.isValid(dateValue)) return 'Invalid date';
return date.format(dateValue, format);
};
// Date validation
const isWeekend = (dateValue) => {
const dayOfWeek = date.getDayOfWeek(dateValue);
return dayOfWeek === 0 || dayOfWeek === 6; // Sunday or Saturday
};
// Business days calculation
const addBusinessDays = (startDate, days) => {
let result = startDate;
let addedDays = 0;
while (addedDays < days) {
result = date.addDays(result, 1);
if (!isWeekend(result)) {
addedDays++;
}
}
return result;
};
</script>Composable for smooth scrolling navigation and programmatic page navigation.
/**
* Smooth scrolling navigation composable
* @returns GoTo instance for smooth scrolling functionality
*/
function useGoTo(): GoToInstance;
interface GoToInstance {
/** Scroll to target element or position */
goTo: (
target: string | number | Element,
options?: GoToOptions
) => Promise<void>;
}
interface GoToOptions {
/** Container element to scroll within */
container?: string | Element;
/** Scroll duration in milliseconds */
duration?: number;
/** Easing function */
easing?: string | ((t: number) => number);
/** Offset from target position */
offset?: number;
}Usage Examples:
<template>
<div>
<!-- Navigation menu -->
<v-app-bar fixed>
<v-btn @click="goTo.goTo('#section1')">Section 1</v-btn>
<v-btn @click="goTo.goTo('#section2')">Section 2</v-btn>
<v-btn @click="goTo.goTo('#section3')">Section 3</v-btn>
<v-btn @click="scrollToTop">Top</v-btn>
</v-app-bar>
<!-- Page sections -->
<v-main>
<section id="section1" class="section">
<v-container>
<h2>Section 1</h2>
<p>Content for section 1...</p>
<v-btn @click="scrollToNext('#section2')">Next Section</v-btn>
</v-container>
</section>
<section id="section2" class="section">
<v-container>
<h2>Section 2</h2>
<p>Content for section 2...</p>
<v-btn @click="scrollToNext('#section3')">Next Section</v-btn>
</v-container>
</section>
<section id="section3" class="section">
<v-container>
<h2>Section 3</h2>
<p>Content for section 3...</p>
<v-btn @click="scrollToTop">Back to Top</v-btn>
</v-container>
</section>
<!-- Scrollable container -->
<v-card class="ma-4">
<v-card-title>
Scrollable List
<v-spacer />
<v-btn size="small" @click="scrollToListItem(50)">
Go to Item 50
</v-btn>
</v-card-title>
<v-card-text>
<div ref="scrollContainer" class="scroll-container">
<div
v-for="n in 100"
:key="n"
:id="`item-${n}`"
class="list-item"
:class="{ highlighted: n === 50 }"
>
Item {{ n }}
</div>
</div>
</v-card-text>
</v-card>
<!-- Smooth scroll with custom options -->
<v-card class="ma-4">
<v-card-title>Custom Scroll Options</v-card-title>
<v-card-text>
<v-btn-group>
<v-btn @click="customScroll('fast')">Fast</v-btn>
<v-btn @click="customScroll('slow')">Slow</v-btn>
<v-btn @click="customScroll('bounce')">Bounce</v-btn>
</v-btn-group>
</v-card-text>
</v-card>
</v-main>
<!-- Floating action button for scroll to top -->
<v-fab
v-show="showScrollButton"
location="bottom right"
icon="mdi-chevron-up"
@click="scrollToTop"
/>
</div>
</template>
<script setup>
const goTo = useGoTo();
const scrollContainer = ref();
const showScrollButton = ref(false);
const scrollToTop = async () => {
await goTo.goTo(0, {
duration: 800,
easing: 'easeInOutCubic'
});
};
const scrollToNext = async (target) => {
await goTo.goTo(target, {
duration: 600,
offset: -64, // Account for app bar height
easing: 'easeOutQuart'
});
};
const scrollToListItem = async (itemNumber) => {
await goTo.goTo(`#item-${itemNumber}`, {
container: scrollContainer.value,
duration: 400,
easing: 'easeInOutQuad'
});
};
const customScroll = async (type) => {
const target = '#section2';
switch (type) {
case 'fast':
await goTo.goTo(target, {
duration: 200,
easing: 'linear'
});
break;
case 'slow':
await goTo.goTo(target, {
duration: 2000,
easing: 'easeInOutSine'
});
break;
case 'bounce':
await goTo.goTo(target, {
duration: 1000,
easing: (t) => {
// Custom bounce easing
return t < 0.5
? 2 * t * t
: -1 + (4 - 2 * t) * t;
}
});
break;
}
};
// Track scroll position for showing scroll button
const handleScroll = () => {
showScrollButton.value = window.scrollY > 300;
};
// Programmatic navigation with animations
const navigateWithAnimation = async (route) => {
// Scroll to top before navigation
await goTo.goTo(0, { duration: 300 });
// Navigate to new route
await router.push(route);
};
onMounted(() => {
window.addEventListener('scroll', handleScroll);
});
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll);
});
</script>
<style>
.section {
min-height: 100vh;
padding: 80px 0;
}
.section:nth-child(odd) {
background-color: #f5f5f5;
}
.scroll-container {
height: 300px;
overflow-y: auto;
}
.list-item {
padding: 16px;
border-bottom: 1px solid #e0e0e0;
transition: background-color 0.2s;
}
.list-item.highlighted {
background-color: #e3f2fd;
}
.list-item:hover {
background-color: #f0f0f0;
}
</style>Composable for managing layout dimensions, spacing, and responsive layout calculations.
/**
* Layout management composable
* @returns Layout instance for layout dimensions and calculations
*/
function useLayout(): LayoutInstance;
interface LayoutInstance {
/** Layout dimensions */
dimensions: Ref<LayoutDimensions>;
/** Get layout item by name */
getLayoutItem: (name: string) => LayoutItem | undefined;
/** Register layout item */
register: (name: string, item: LayoutItem) => void;
/** Unregister layout item */
unregister: (name: string) => void;
/** Main content area */
mainStyles: ComputedRef<Record<string, string>>;
}
interface LayoutDimensions {
/** Total layout width */
width: number;
/** Total layout height */
height: number;
/** Available content width */
contentWidth: number;
/** Available content height */
contentHeight: number;
}
interface LayoutItem {
/** Layout item ID */
id: string;
/** Item size */
size: number;
/** Item position */
position: 'top' | 'bottom' | 'left' | 'right';
/** Whether item is visible */
visible: boolean;
}Usage Examples:
<template>
<v-app>
<!-- App bar -->
<v-app-bar
ref="appBar"
color="primary"
height="64"
fixed
>
<v-app-bar-nav-icon @click="drawer = !drawer" />
<v-toolbar-title>Layout Demo</v-toolbar-title>
<v-spacer />
<v-btn icon @click="toggleBottomNav">
<v-icon>mdi-view-dashboard</v-icon>
</v-btn>
</v-app-bar>
<!-- Navigation drawer -->
<v-navigation-drawer
v-model="drawer"
ref="navigationDrawer"
:width="drawerWidth"
temporary
>
<v-list>
<v-list-item title="Home" prepend-icon="mdi-home" />
<v-list-item title="About" prepend-icon="mdi-information" />
<v-list-item title="Contact" prepend-icon="mdi-phone" />
</v-list>
</v-navigation-drawer>
<!-- Main content -->
<v-main :style="layout.mainStyles.value">
<v-container>
<!-- Layout information -->
<v-card class="mb-4">
<v-card-title>Layout Information</v-card-title>
<v-card-text>
<v-row>
<v-col cols="6">
<strong>Total Size:</strong><br>
{{ layout.dimensions.value.width }} x {{ layout.dimensions.value.height }}
</v-col>
<v-col cols="6">
<strong>Content Size:</strong><br>
{{ layout.dimensions.value.contentWidth }} x {{ layout.dimensions.value.contentHeight }}
</v-col>
</v-row>
</v-card-text>
</v-card>
<!-- Responsive grid based on layout -->
<v-row>
<v-col
v-for="item in gridItems"
:key="item.id"
:cols="getColumnSize()"
>
<v-card height="200">
<v-card-title>{{ item.title }}</v-card-title>
<v-card-text>{{ item.description }}</v-card-text>
</v-card>
</v-col>
</v-row>
<!-- Adaptive content based on available space -->
<v-card class="mt-4">
<v-card-title>Adaptive Content</v-card-title>
<v-card-text>
<div :class="getContentLayoutClass()">
<div class="content-section">
<h3>Primary Content</h3>
<p>This content adapts based on available layout space.</p>
</div>
<div class="sidebar-section" v-if="shouldShowSidebar">
<h4>Sidebar</h4>
<p>Additional information shown when space allows.</p>
</div>
</div>
</v-card-text>
</v-card>
</v-container>
</v-main>
<!-- Bottom navigation (conditional) -->
<v-bottom-navigation
v-if="showBottomNav"
ref="bottomNavigation"
v-model="bottomNavValue"
height="64"
>
<v-btn value="home">
<v-icon>mdi-home</v-icon>
<span>Home</span>
</v-btn>
<v-btn value="search">
<v-icon>mdi-magnify</v-icon>
<span>Search</span>
</v-btn>
<v-btn value="profile">
<v-icon>mdi-account</v-icon>
<span>Profile</span>
</v-btn>
</v-bottom-navigation>
<!-- System bar (conditional) -->
<v-system-bar
v-if="showSystemBar"
ref="systemBar"
height="24"
color="primary"
dark
>
<v-icon size="small">mdi-wifi</v-icon>
<v-icon size="small">mdi-signal</v-icon>
<v-icon size="small">mdi-battery</v-icon>
<span class="text-caption ml-2">12:34 PM</span>
</v-system-bar>
</v-app>
</template>
<script setup>
const layout = useLayout();
const display = useDisplay();
const drawer = ref(false);
const drawerWidth = ref(280);
const showBottomNav = ref(false);
const showSystemBar = ref(false);
const bottomNavValue = ref('home');
const gridItems = ref([
{ id: 1, title: 'Card 1', description: 'First card content' },
{ id: 2, title: 'Card 2', description: 'Second card content' },
{ id: 3, title: 'Card 3', description: 'Third card content' },
{ id: 4, title: 'Card 4', description: 'Fourth card content' },
]);
// Reactive column sizing based on layout
const getColumnSize = () => {
const contentWidth = layout.dimensions.value.contentWidth;
if (contentWidth < 600) return 12;
if (contentWidth < 900) return 6;
if (contentWidth < 1200) return 4;
return 3;
};
// Adaptive layout classes
const getContentLayoutClass = () => {
const contentWidth = layout.dimensions.value.contentWidth;
return contentWidth > 800 ? 'd-flex' : 'd-block';
};
// Conditional sidebar display
const shouldShowSidebar = computed(() => {
return layout.dimensions.value.contentWidth > 800;
});
// Layout item registration
const registerLayoutItems = () => {
// These would typically be registered by the components themselves
layout.register('appBar', {
id: 'app-bar',
size: 64,
position: 'top',
visible: true
});
if (showBottomNav.value) {
layout.register('bottomNav', {
id: 'bottom-navigation',
size: 64,
position: 'bottom',
visible: true
});
}
if (showSystemBar.value) {
layout.register('systemBar', {
id: 'system-bar',
size: 24,
position: 'top',
visible: true
});
}
};
const toggleBottomNav = () => {
showBottomNav.value = !showBottomNav.value;
if (showBottomNav.value) {
layout.register('bottomNav', {
id: 'bottom-navigation',
size: 64,
position: 'bottom',
visible: true
});
} else {
layout.unregister('bottomNav');
}
};
// Watch layout changes
watch(() => layout.dimensions.value, (newDimensions, oldDimensions) => {
console.log('Layout changed:', {
from: oldDimensions,
to: newDimensions
});
}, { deep: true });
// Responsive drawer width
watchEffect(() => {
if (display.xs.value) {
drawerWidth.value = Math.min(280, layout.dimensions.value.width * 0.8);
} else {
drawerWidth.value = 280;
}
});
onMounted(() => {
registerLayoutItems();
});
</script>
<style>
.content-section {
flex: 1;
margin-right: 24px;
}
.sidebar-section {
width: 200px;
background: #f5f5f5;
padding: 16px;
border-radius: 4px;
}
@media (max-width: 800px) {
.content-section {
margin-right: 0;
margin-bottom: 16px;
}
}
</style>Composable for registering and managing keyboard shortcuts throughout the application.
/**
* Keyboard shortcut management composable
* @returns Hotkey instance for managing keyboard shortcuts
*/
function useHotkey(): HotkeyInstance;
interface HotkeyInstance {
/** Register a hotkey */
register: (
keys: string | string[],
handler: (event: KeyboardEvent) => void,
options?: HotkeyOptions
) => () => void;
/** Unregister a hotkey */
unregister: (keys: string | string[]) => void;
/** Get all registered hotkeys */
getHotkeys: () => HotkeyRegistration[];
/** Enable/disable all hotkeys */
enabled: Ref<boolean>;
}
interface HotkeyOptions {
/** Scope for the hotkey */
scope?: string;
/** Element to bind the hotkey to */
element?: Element | string;
/** Prevent default behavior */
preventDefault?: boolean;
/** Stop event propagation */
stopPropagation?: boolean;
/** Description for the hotkey */
description?: string;
}
interface HotkeyRegistration {
keys: string[];
handler: (event: KeyboardEvent) => void;
options: HotkeyOptions;
scope: string;
}Usage Examples:
<template>
<div>
<!-- Hotkey status display -->
<v-card class="mb-4">
<v-card-title>
Keyboard Shortcuts
<v-spacer />
<v-switch
v-model="hotkey.enabled.value"
label="Enable Shortcuts"
inset
/>
</v-card-title>
<v-card-text>
<v-list density="compact">
<v-list-item
v-for="shortcut in registeredHotkeys"
:key="shortcut.keys.join('+')"
>
<v-list-item-title>
<v-chip size="small" variant="outlined">
{{ shortcut.keys.join(' + ') }}
</v-chip>
</v-list-item-title>
<v-list-item-subtitle>{{ shortcut.options.description }}</v-list-item-subtitle>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
<!-- Demo content -->
<v-row>
<v-col cols="12" md="6">
<v-card>
<v-card-title>Text Editor</v-card-title>
<v-card-text>
<v-textarea
ref="textEditor"
v-model="editorContent"
label="Press Ctrl+S to save, Ctrl+Z to undo"
rows="10"
/>
<v-chip-group class="mt-2">
<v-chip v-if="lastAction" color="success">
{{ lastAction }}
</v-chip>
</v-chip-group>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" md="6">
<v-card>
<v-card-title>Navigation</v-card-title>
<v-card-text>
<p>Use keyboard shortcuts to navigate:</p>
<v-list density="compact">
<v-list-item title="Home" subtitle="Press '1'" />
<v-list-item title="About" subtitle="Press '2'" />
<v-list-item title="Contact" subtitle="Press '3'" />
<v-list-item title="Help" subtitle="Press '?'" />
</v-list>
<v-chip v-if="currentPage" color="primary" class="mt-2">
Current: {{ currentPage }}
</v-chip>
</v-card-text>
</v-card>
</v-col>
</v-row>
<!-- Action buttons with hotkeys -->
<v-card class="mt-4">
<v-card-title>Actions</v-card-title>
<v-card-text>
<v-btn-group>
<v-btn @click="newFile" prepend-icon="mdi-file-plus">
New (Ctrl+N)
</v-btn>
<v-btn @click="openFile" prepend-icon="mdi-folder-open">
Open (Ctrl+O)
</v-btn>
<v-btn @click="saveFile" prepend-icon="mdi-content-save" color="primary">
Save (Ctrl+S)
</v-btn>
</v-btn-group>
</v-card-text>
</v-card>
<!-- Modal with scoped hotkeys -->
<v-dialog v-model="dialogOpen" max-width="500">
<template #activator="{ props }">
<v-btn v-bind="props" class="mt-4">
Open Dialog (Press Esc to close when open)
</v-btn>
</template>
<v-card>
<v-card-title>Modal Dialog</v-card-title>
<v-card-text>
This dialog has scoped hotkeys. Press Esc to close or Enter to confirm.
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn @click="dialogOpen = false">Cancel</v-btn>
<v-btn color="primary" @click="confirmDialog">Confirm</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Help overlay -->
<v-overlay v-model="showHelp" class="d-flex align-center justify-center">
<v-card max-width="600">
<v-card-title>
Keyboard Shortcuts Help
<v-spacer />
<v-btn icon size="small" @click="showHelp = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<v-simple-table>
<template #default>
<tbody>
<tr v-for="shortcut in allHotkeys" :key="shortcut.keys.join('+')">
<td>
<code>{{ shortcut.keys.join(' + ') }}</code>
</td>
<td>{{ shortcut.options.description }}</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-card-text>
</v-card>
</v-overlay>
</div>
</template>
<script setup>
const hotkey = useHotkey();
const editorContent = ref('Type something here...');
const lastAction = ref('');
const currentPage = ref('Home');
const dialogOpen = ref(false);
const showHelp = ref(false);
const textEditor = ref();
const registeredHotkeys = computed(() => hotkey.getHotkeys());
const allHotkeys = computed(() => hotkey.getHotkeys());
// File operations
const newFile = () => {
editorContent.value = '';
lastAction.value = 'New file created';
setTimeout(() => lastAction.value = '', 2000);
};
const openFile = () => {
lastAction.value = 'File opened';
setTimeout(() => lastAction.value = '', 2000);
};
const saveFile = () => {
lastAction.value = 'File saved';
setTimeout(() => lastAction.value = '', 2000);
};
const confirmDialog = () => {
lastAction.value = 'Dialog confirmed';
dialogOpen.value = false;
setTimeout(() => lastAction.value = '', 2000);
};
// Register global hotkeys
onMounted(() => {
// File operations
hotkey.register(['ctrl+n', 'cmd+n'], newFile, {
description: 'Create new file',
preventDefault: true
});
hotkey.register(['ctrl+o', 'cmd+o'], openFile, {
description: 'Open file',
preventDefault: true
});
hotkey.register(['ctrl+s', 'cmd+s'], saveFile, {
description: 'Save file',
preventDefault: true
});
// Navigation shortcuts
hotkey.register('1', () => {
currentPage.value = 'Home';
}, {
description: 'Go to Home page'
});
hotkey.register('2', () => {
currentPage.value = 'About';
}, {
description: 'Go to About page'
});
hotkey.register('3', () => {
currentPage.value = 'Contact';
}, {
description: 'Go to Contact page'
});
// Help
hotkey.register(['?', 'F1'], () => {
showHelp.value = true;
}, {
description: 'Show help'
});
// Focus text editor
hotkey.register('ctrl+e', () => {
textEditor.value?.focus();
}, {
description: 'Focus text editor',
preventDefault: true
});
});
// Register scoped hotkeys for dialog
watch(dialogOpen, (isOpen) => {
if (isOpen) {
// Register dialog-specific hotkeys
const unregisterEsc = hotkey.register('escape', () => {
dialogOpen.value = false;
}, {
scope: 'dialog',
description: 'Close dialog'
});
const unregisterEnter = hotkey.register('enter', confirmDialog, {
scope: 'dialog',
description: 'Confirm dialog',
preventDefault: true
});
// Cleanup when dialog closes
watch(dialogOpen, (stillOpen) => {
if (!stillOpen) {
unregisterEsc();
unregisterEnter();
}
}, { once: true });
}
});
// Global escape for help
watch(showHelp, (isOpen) => {
if (isOpen) {
const unregisterEsc = hotkey.register('escape', () => {
showHelp.value = false;
}, {
scope: 'help',
description: 'Close help'
});
watch(showHelp, (stillOpen) => {
if (!stillOpen) {
unregisterEsc();
}
}, { once: true });
}
});
</script>
<style>
code {
background: rgba(0,0,0,0.1);
padding: 2px 6px;
border-radius: 4px;
font-family: 'Roboto Mono', monospace;
font-size: 0.875em;
}
.v-simple-table tbody tr td {
padding: 8px 16px;
}
</style>// Composable return types
type ThemeInstance = {
current: Ref<ThemeDefinition>;
themes: Ref<Record<string, ThemeDefinition>>;
isDisabled: Ref<boolean>;
name: Ref<string>;
computedThemes: ComputedRef<Record<string, ThemeDefinition>>;
themeClasses: Ref<Record<string, boolean>>;
styles: ComputedRef<string>;
};
type DisplayInstance = {
xs: Ref<boolean>;
sm: Ref<boolean>;
md: Ref<boolean>;
lg: Ref<boolean>;
xl: Ref<boolean>;
xxl: Ref<boolean>;
smAndUp: Ref<boolean>;
mdAndUp: Ref<boolean>;
lgAndUp: Ref<boolean>;
xlAndUp: Ref<boolean>;
smAndDown: Ref<boolean>;
mdAndDown: Ref<boolean>;
lgAndDown: Ref<boolean>;
xlAndDown: Ref<boolean>;
name: Ref<string>;
height: Ref<number>;
width: Ref<number>;
mobile: Ref<boolean>;
mobileBreakpoint: Ref<number | string>;
thresholds: Ref<DisplayThresholds>;
platform: Ref<'android' | 'ios' | 'desktop'>;
touch: Ref<boolean>;
ssr: boolean;
};
type LocaleInstance = {
current: Ref<string>;
fallback: Ref<string>;
locales: Ref<Record<string, LocaleMessages>>;
messages: Ref<Record<string, LocaleMessages>>;
t: (key: string, ...params: unknown[]) => string;
n: (value: number) => string;
provide: Record<string, any>;
isRtl: Ref<boolean>;
};
type RtlInstance = {
isRtl: Ref<boolean>;
rtl: Ref<boolean>;
rtlClasses: Ref<string>;
};
type DefaultsInstance = {
getDefaults(): Record<string, any>;
reset(): void;
global: Record<string, any>;
};
type DateInstance = {
adapter: DateAdapter;
locale: Ref<string>;
format: (date: any, formatString: string) => string;
parse: (value: string, format: string) => any;
addDays: (date: any, days: number) => any;
addMonths: (date: any, months: number) => any;
addYears: (date: any, years: number) => any;
startOfDay: (date: any) => any;
endOfDay: (date: any) => any;
startOfMonth: (date: any) => any;
endOfMonth: (date: any) => any;
isEqual: (date1: any, date2: any) => boolean;
isBefore: (date1: any, date2: any) => boolean;
isAfter: (date1: any, date2: any) => boolean;
isSameDay: (date1: any, date2: any) => boolean;
isSameMonth: (date1: any, date2: any) => boolean;
getYear: (date: any) => number;
getMonth: (date: any) => number;
getDate: (date: any) => number;
getDayOfWeek: (date: any) => number;
isValid: (date: any) => boolean;
};
type GoToInstance = {
goTo: (
target: string | number | Element,
options?: GoToOptions
) => Promise<void>;
};
type LayoutInstance = {
dimensions: Ref<LayoutDimensions>;
getLayoutItem: (name: string) => LayoutItem | undefined;
register: (name: string, item: LayoutItem) => void;
unregister: (name: string) => void;
mainStyles: ComputedRef<Record<string, string>>;
};
type HotkeyInstance = {
register: (
keys: string | string[],
handler: (event: KeyboardEvent) => void,
options?: HotkeyOptions
) => () => void;
unregister: (keys: string | string[]) => void;
getHotkeys: () => HotkeyRegistration[];
enabled: Ref<boolean>;
};
// Configuration types
type EasingFunction = (t: number) => number;
type Platform = 'android' | 'ios' | 'desktop';
type BreakpointName = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';Install with Tessl CLI
npx tessl i tessl/npm-vuetify