Skip to content
Merged
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Theme presets: Tooltips can now be globally styled with the "default", "primevue", or "vuetify" presets. Switching themes at runtime is supported.

## [1.2.2] - 2025-10-27

### Fixed
Expand Down
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const app = createApp(App)

// Configure global defaults for all tooltips
app.use(VueCustomTooltip, {
theme: 'default', // or 'vuetify' or 'primevue'
globalConfig: {
position: 'top', // Default position for all tooltips
trigger: 'hover', // Default trigger behavior
Expand Down Expand Up @@ -381,6 +382,59 @@ The `v-tooltip` directive is also fully typed when you install the plugin. TypeS
</template>
```

## Theme Presets

Vue Custom Tooltip supports built-in theme presets for easy integration with popular UI frameworks, as well as a default theme:

- **default**: Uses the component's original built-in styles (no extra CSS loaded)
- **primevue**: Styles inspired by PrimeVue's design system
- **vuetify**: Styles inspired by Vuetify's Material Design implementation

You can select a theme globally when registering the plugin:

```typescript
import { VueCustomTooltip } from '@borstihd/vue-custom-tooltip'
import { createApp } from 'vue'
import App from './App.vue'
import '@borstihd/vue-custom-tooltip/dist/style.css'

const app = createApp(App)

// Use a theme preset
app.use(VueCustomTooltip, {
theme: 'primevue' // or 'vuetify' or 'default'
})

// The default theme is used if you omit the theme option:
app.use(VueCustomTooltip) // same as theme: 'default'

app.mount('#app')
```

You can also switch themes at runtime:

```typescript
import { setTooltipGlobalTheme } from '@borstihd/vue-custom-tooltip'

setTooltipGlobalTheme('vuetify') // Switch to Vuetify theme
setTooltipGlobalTheme('default') // Revert to default styles
```

### Customizing Theme Styles

Each theme uses CSS custom properties (variables) for easy customization. You can override these in your global CSS:

```css
:root {
/* Example for PrimeVue theme */
--vct-primevue-background: #1a1a1a;
--vct-primevue-text-color: #fff;
--vct-primevue-border-radius: 8px;
}
```

See the [src/styles/themes/README.md](src/styles/themes/README.md) for a full list of theme variables and instructions for creating your own custom themes.

## Styling

The tooltip uses CSS custom properties for theming. You can customize the appearance by overriding these variables:
Expand Down
139 changes: 139 additions & 0 deletions THEME_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Theme Configuration Guide

## Overview

Vue Custom Tooltip supports predefined UI framework themes! You can easily apply PrimeVue or Vuetify styling to all your tooltips with a simple configuration option.

## Available Themes

- **`default`**: The built-in theme using the component's original styles (no additional CSS loaded)
- **`primevue`**: Styles inspired by PrimeVue's design system
- **`vuetify`**: Styles inspired by Vuetify's Material Design implementation

## Basic Usage

### Option 1: Global Theme Configuration

Apply a theme to all tooltips in your application:

```typescript
import { createApp } from 'vue'
import { VueCustomTooltip } from 'vue-custom-tooltip'
import App from './App.vue'

const app = createApp(App)

app.use(VueCustomTooltip, {
theme: 'primevue' // or 'vuetify' or 'default'
})

app.mount('#app')
```

### Option 2: Theme with Global Config

Combine theme styling with global configuration:

```typescript
app.use(VueCustomTooltip, {
theme: 'primevue',
globalConfig: {
position: 'top',
trigger: 'hover',
showDelay: 200,
hideDelay: 150,
dark: 'auto', // Supports auto-detection, true, or false
showArrow: true,
offset: 12,
maxWidth: '300px',
},
})
```

### Option 3: No Theme (Default Styling)

If you don't specify a theme, the default tooltip styling will be used:

```typescript
// These are equivalent
app.use(VueCustomTooltip)
app.use(VueCustomTooltip, { theme: 'default' })
```

## Programmatic Theme Control

You can also change the theme programmatically:

```typescript
import { getTooltipGlobalTheme, setTooltipGlobalTheme } from 'vue-custom-tooltip'

// Change theme at runtime
setTooltipGlobalTheme('vuetify')

// Get current theme
const currentTheme = getTooltipGlobalTheme()

// Revert to default (uses component's built-in styles)
setTooltipGlobalTheme('default')
// or
setTooltipGlobalTheme(undefined)
```

## Theme Features

### Dark Mode Support

All themes automatically support dark mode through:
1. **Auto detection** (`dark: 'auto'`): Responds to Tailwind's `.dark` class or `prefers-color-scheme`
2. **Forced dark mode** (`dark: true`): Always use dark theme
3. **Forced light mode** (`dark: false`): Always use light theme

```typescript
// Auto-detect dark mode
app.use(VueCustomTooltip, {
theme: 'primevue',
globalConfig: {
dark: 'auto', // Default
},
})

// Force dark mode
app.use(VueCustomTooltip, {
theme: 'vuetify',
globalConfig: {
dark: true,
},
})
```

### CSS Custom Properties

Each theme uses CSS custom properties that you can override in your own styles:

```css
/* Override PrimeVue theme colors */
:root {
--vct-primevue-background: #1a1a1a;
--vct-primevue-text-color: #ffffff;
--vct-primevue-border-radius: 8px;
}

/* Override Vuetify theme colors */
:root {
--vct-vuetify-background: rgba(50, 50, 50, 0.95);
--vct-vuetify-text-color: #e0e0e0;
--vct-vuetify-font-family: 'Custom Font', sans-serif;
}
```

## Notes

- Themes are injected as `<style>` elements in the document `<head>`
- Only one theme can be active at a time
- Changing themes at runtime will automatically remove the previous theme styles
- Themes work with both the component API (`<Tooltip>`) and directive API (`v-tooltip`)
- All theme styles respect the tooltip's position, trigger, and other configuration options

## Creating Custom Themes

See [styles/themes/README.md](../src/styles/themes/README.md) for information on creating your own custom themes.
2 changes: 2 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref } from 'vue'
import Button from '@/components/Button.vue'
import PresetSwitcher from '@/components/PresetSwitcher.vue'
import ThemeToggle from '@/components/ThemeToggle.vue'
import TooltipDirectiveBenchmark from '@/components/tooltip/TooltipDirectiveBenchmark.vue'
import TooltipDirectiveExample from '@/components/tooltip/TooltipDirectiveExample.vue'
Expand Down Expand Up @@ -40,6 +41,7 @@ const githubRepo = packageJson.repository.url.replace('.git', '')
TypeScript
</span>

<PresetSwitcher />
<ThemeToggle />
</div>
</div>
Expand Down
38 changes: 38 additions & 0 deletions src/components/PresetSwitcher.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script setup lang="ts">
import type { TooltipTheme } from '@/types/tooltip'
import { ref } from 'vue'
import { getTooltipGlobalTheme, setTooltipGlobalTheme } from '../index'

// Theme switching logic
const themeOptions = [
{ label: 'Default', value: 'default' },
{ label: 'PrimeVue', value: 'primevue' },
{ label: 'Vuetify', value: 'vuetify' },
]
const currentTheme = ref<TooltipTheme>(getTooltipGlobalTheme() || 'default')
async function handleThemeChange(e: Event) {
const value = (e.target as HTMLSelectElement).value as TooltipTheme
setTooltipGlobalTheme(value)
}
</script>

<template>
<div class="relative inline-block">
<label for="theme-switcher" class="sr-only">Tooltip Theme</label>
<select
id="theme-switcher"
v-model="currentTheme"
class="appearance-none flex gap-2 px-2 h-10 items-center rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 pr-8"
@change="handleThemeChange"
>
<option v-for="opt in themeOptions" :key="opt.value" :value="opt.value">
{{ opt.label }}
</option>
</select>
<span class="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500">
<svg width="18" height="18" fill="none" viewBox="0 0 20 20">
<path d="M6 8l4 4 4-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</span>
</div>
</template>
45 changes: 2 additions & 43 deletions src/components/ThemeToggle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ watchEffect((onCleanup) => {
<template>
<button
type="button"
class="theme-toggle"
class="flex gap-2 px-4 h-10 items-center rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
:aria-label="`Switch to ${currentTheme === 'light' ? 'dark' : currentTheme === 'dark' ? 'system' : 'light'} mode`"
:title="`Current: ${currentTheme} mode`"
@click="toggleTheme"
Expand Down Expand Up @@ -132,47 +132,6 @@ watchEffect((onCleanup) => {
<line x1="12" y1="17" x2="12" y2="21" />
</svg>

<span class="theme-label">{{ currentTheme }}</span>
<span class="capitalize">{{ currentTheme }}</span>
</button>
</template>

<style scoped>
.theme-toggle {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border: 1px solid #e5e7eb;
border-radius: 0.375rem;
background-color: #ffffff;
color: #374151;
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.875rem;
font-weight: 500;
}

.theme-toggle:hover {
background-color: #f9fafb;
border-color: #d1d5db;
}

.theme-toggle:active {
transform: scale(0.98);
}

.dark .theme-toggle {
background-color: #1f2937;
color: #f9fafb;
border-color: #374151;
}

.dark .theme-toggle:hover {
background-color: #374151;
border-color: #4b5563;
}

.theme-label {
text-transform: capitalize;
}
</style>
6 changes: 6 additions & 0 deletions src/components/tooltip/Tooltip.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
useTooltipProps,
useTooltipVisibility,
} from '../../composables'
import { getTooltipGlobalThemeRef } from '../../config/index'

/**
* Generic Tooltip Component
Expand Down Expand Up @@ -159,6 +160,9 @@ const {
// Computed properties
const hasContentSlot = computed(() => !!slots.content)

// Get global theme
const globalTheme = getTooltipGlobalThemeRef()

const tooltipClasses = computed(() => [
'custom-tooltip',
`tooltip-${actualPosition.value}`,
Expand All @@ -169,6 +173,8 @@ const tooltipClasses = computed(() => [
'tooltip-light': effectiveDark.value === false,
'tooltip-auto': effectiveDark.value === 'auto',
},
// Only apply theme class if it's not 'default' (default uses component's built-in styles)
globalTheme.value && globalTheme.value !== 'default' ? `tooltip-theme-${globalTheme.value}` : '',
effectiveTooltipClass.value,
])

Expand Down
Loading