Skip to content

Component Library Overview

God Panel provides a comprehensive component library built with Vue 3 and TypeScript, offering reusable UI components for building modern admin interfaces.

Component Categories

🎨 UI Components

  • Buttons: Primary, secondary, ghost, and icon buttons
  • Cards: Content containers with headers, bodies, and footers
  • Forms: Input fields, textareas, selects, checkboxes, and radios
  • Modals: Dialog boxes for confirmations and content display
  • Notifications: Toast messages and alerts

📊 Data Display

  • Tables: Sortable, filterable data tables with pagination
  • Charts: Integration with Chart.js for data visualization
  • Stats: Metric cards and KPI displays
  • Lists: Ordered and unordered lists with icons

🧭 Navigation

  • Sidebar: Collapsible navigation with icons
  • Breadcrumbs: Navigation trail components
  • Tabs: Tabbed interface components
  • Pagination: Page navigation controls

🔧 Form Controls

  • Date Picker: Calendar-based date selection
  • File Upload: Drag-and-drop file upload
  • Rich Text Editor: WYSIWYG content editor
  • Color Picker: Color selection tool

Component Architecture

Base Component Structure

vue
<!-- components/ui/BaseComponent.vue -->
<template>
  <component
    :is="tag"
    :class="componentClasses"
    v-bind="$attrs"
  >
    <slot />
  </component>
</template>

<script setup>
interface Props {
  tag?: string
  variant?: string
  size?: 'sm' | 'md' | 'lg'
  disabled?: boolean
  loading?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  tag: 'div',
  variant: 'default',
  size: 'md',
  disabled: false,
  loading: false
})

const componentClasses = computed(() => [
  'base-component',
  `base-component-${props.variant}`,
  `base-component-${props.size}`,
  {
    'base-component-disabled': props.disabled,
    'base-component-loading': props.loading
  }
])
</script>

Theme Integration

All components are built with CSS custom properties for seamless theme integration:

css
/* Component theming */
.btn-primary {
  background-color: var(--color-primary);
  color: var(--text-inverse);
  border-color: var(--color-primary);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-4);
  font-weight: var(--font-medium);
  transition: all 0.2s ease;
}

[data-theme="dark"] .btn-primary {
  background-color: var(--color-primary-600);
  border-color: var(--color-primary-600);
}

Component Usage

Import and Registration

typescript
// Auto-import (recommended)
<script setup>
// Components are auto-imported via Nuxt
const showModal = ref(false)
</script>

<!-- Import specific components -->
<script setup>
import { Button, Card, Input } from '#components'
</script>

Global Registration

typescript
// plugins/components.client.ts
import { defineNuxtPlugin } from '#app'

export default defineNuxtPlugin(() => {
  // Components are auto-registered via Nuxt
})

Component Props and Events

Standard Props

Most components accept these standard props:

typescript
interface BaseProps {
  // Size variants
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'

  // Visual variants
  variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info'

  // State
  disabled?: boolean
  loading?: boolean
  readonly?: boolean

  // Styling
  class?: string
  style?: Record<string, string>
}

Event Handling

vue
<template>
  <Button
    @click="handleClick"
    @focus="handleFocus"
    @blur="handleBlur"
  >
    Click me
  </Button>
</template>

<script setup>
const handleClick = (event: MouseEvent) => {
  console.log('Button clicked', event)
}

const handleFocus = () => {
  console.log('Button focused')
}
</script>

Responsive Design

All components are built mobile-first and responsive:

css
/* Mobile-first approach */
.component {
  width: 100%;
  padding: var(--space-2);
}

/* Tablet */
@media (min-width: 768px) {
  .component {
    width: auto;
    padding: var(--space-3);
  }
}

/* Desktop */
@media (min-width: 1024px) {
  .component {
    padding: var(--space-4);
  }
}

Accessibility

Components follow WCAG 2.1 guidelines:

Keyboard Navigation

vue
<template>
  <Button
    ref="buttonRef"
    @keydown.enter="handleEnter"
    @keydown.space="handleSpace"
  >
    Accessible Button
  </Button>
</template>

<script setup>
const buttonRef = ref()

const handleEnter = (event: KeyboardEvent) => {
  event.preventDefault()
  // Handle enter key
}

const handleSpace = (event: KeyboardEvent) => {
  event.preventDefault()
  // Handle space key
}
</script>

Screen Reader Support

vue
<template>
  <div role="button" :aria-label="label" tabindex="0">
    <slot />
  </div>
</template>

<script setup>
interface Props {
  label: string
  disabled?: boolean
}

const props = defineProps<Props>()

// Announce state changes
const announceToScreenReader = (message: string) => {
  const announcement = document.createElement('div')
  announcement.setAttribute('aria-live', 'polite')
  announcement.textContent = message
  document.body.appendChild(announcement)

  setTimeout(() => {
    document.body.removeChild(announcement)
  }, 1000)
}
</script>

Component Testing

Unit Tests

typescript
// test/components/Button.test.ts
import { mount } from '@vue/test-utils'
import Button from '~/components/ui/Button.vue'

describe('Button Component', () => {
  test('renders correctly', () => {
    const wrapper = mount(Button, {
      props: {
        variant: 'primary'
      },
      slots: {
        default: 'Click me'
      }
    })

    expect(wrapper.text()).toBe('Click me')
    expect(wrapper.classes()).toContain('btn-primary')
  })

  test('emits click event', async () => {
    const wrapper = mount(Button)
    await wrapper.trigger('click')

    expect(wrapper.emitted('click')).toBeTruthy()
  })

  test('is disabled when disabled prop is true', () => {
    const wrapper = mount(Button, {
      props: {
        disabled: true
      }
    })

    expect(wrapper.attributes('disabled')).toBeDefined()
    expect(wrapper.classes()).toContain('btn-disabled')
  })
})

Visual Regression Tests

typescript
// test/components/Button.visual.test.ts
describe('Button Visual Tests', () => {
  test('matches snapshot for primary variant', async () => {
    const wrapper = mount(Button, {
      props: {
        variant: 'primary'
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  test('matches snapshot for different sizes', async () => {
    const sizes = ['xs', 'sm', 'md', 'lg', 'xl']

    for (const size of sizes) {
      const wrapper = mount(Button, {
        props: { size }
      })

      expect(wrapper.html()).toMatchSnapshot()
    }
  })
})

Performance Considerations

Lazy Loading

vue
<!-- Only load when needed -->
<template>
  <component
    :is="currentComponent"
    v-if="showComponent"
  />
</template>

<script setup>
const showComponent = ref(false)
const currentComponent = ref('')

const loadComponent = async (componentName: string) => {
  showComponent.value = false

  // Dynamic import
  const component = await import(`~/components/${componentName}.vue`)
  currentComponent.value = component.default

  showComponent.value = true
}
</script>

Component Memoization

typescript
// composables/useComponentCache.ts
const componentCache = new Map()

export const useComponentCache = () => {
  const getCachedComponent = (key: string) => {
    return componentCache.get(key)
  }

  const setCachedComponent = (key: string, component: any) => {
    componentCache.set(key, component)
  }

  const clearCache = () => {
    componentCache.clear()
  }

  return {
    getCachedComponent,
    setCachedComponent,
    clearCache
  }
}

Customization

Custom Component Variants

vue
<!-- components/ui/Button.vue -->
<script setup>
interface Props extends BaseProps {
  customVariant?: string
}

const props = defineProps<Props>()

const componentClasses = computed(() => [
  'btn',
  `btn-${props.variant}`,
  {
    [`btn-custom-${props.customVariant}`]: props.customVariant
  }
])
</script>

<style scoped>
.btn-custom-danger {
  background-color: #dc2626;
  color: white;
}

.btn-custom-danger:hover {
  background-color: #b91c1c;
}
</style>

Theme Override

vue
<!-- Override component styles globally -->
<style>
/* Override button primary color */
.btn-primary {
  background-color: #7c3aed !important;
  border-color: #7c3aed !important;
}

[data-theme="dark"] .btn-primary {
  background-color: #a855f7 !important;
  border-color: #a855f7 !important;
}
</style>

Integration with Libraries

Chart.js Integration

vue
<!-- components/charts/LineChart.vue -->
<template>
  <div class="chart-container">
    <canvas ref="chartRef"></canvas>
  </div>
</template>

<script setup>
import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend
} from 'chart.js'

ChartJS.register(
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend
)

interface Props {
  data: ChartData
  options?: ChartOptions
}

const props = defineProps<Props>()
const chartRef = ref<HTMLCanvasElement>()

onMounted(() => {
  if (chartRef.value) {
    new ChartJS(chartRef.value, {
      type: 'line',
      data: props.data,
      options: {
        responsive: true,
        maintainAspectRatio: false,
        ...props.options
      }
    })
  }
})
</script>

<style scoped>
.chart-container {
  position: relative;
  height: 400px;
  width: 100%;
}
</style>

Vue Router Integration

vue
<!-- components/navigation/NavLink.vue -->
<template>
  <NuxtLink
    :to="to"
    :class="linkClasses"
    :exact="exact"
    v-slot="{ href, navigate, isExactActive }"
  >
    <a
      :href="href"
      @click="navigate"
      :class="[
        linkClasses,
        { 'nav-link-active': isExactActive }
      ]"
    >
      <Icon v-if="icon" :name="icon" />
      <span><slot /></span>
    </a>
  </NuxtLink>
</template>

<script setup>
interface Props {
  to: string
  icon?: string
  exact?: boolean
  variant?: 'default' | 'primary' | 'secondary'
}

const props = withDefaults(defineProps<Props>(), {
  exact: false,
  variant: 'default'
})

const linkClasses = computed(() => [
  'nav-link',
  `nav-link-${props.variant}`
])
</script>

Best Practices

Component Naming

typescript
// ✅ Good naming
const components = {
  'ui-button': Button,
  'ui-card': Card,
  'data-table': DataTable,
  'nav-sidebar': Sidebar
}

// ❌ Avoid
const components = {
  'btn': Button,    // Too short
  'Button': Button,  // PascalCase
  'my-button': Button // Prefixed with personal namespace
}

Props Design

typescript
// ✅ Good props
interface ButtonProps {
  variant: 'primary' | 'secondary' | 'success' | 'warning' | 'error'
  size: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
  disabled: boolean
  loading: boolean
  fullWidth: boolean
}

// ❌ Avoid
interface ButtonProps {
  type: string  // Too generic
  style: object // Too broad
  config: any   // No type safety
}

Event Naming

vue
<!-- ✅ Good events -->
<Button
  @click="handleClick"
  @submit="handleSubmit"
  @close="handleClose"
  @update:modelValue="handleUpdate"
/>

<!-- ❌ Avoid -->
<Button
  @onClick="handleClick"        // Redundant prefix
  @buttonClick="handleClick"     // Component-specific
  @change="handleUpdate"         // Too generic
/>

Migration Guide

From Vue 2 to Vue 3

typescript
// Vue 2 style
export default {
  props: {
    variant: {
      type: String,
      default: 'primary'
    }
  },
  data() {
    return {
      isLoading: false
    }
  },
  methods: {
    handleClick() {
      this.$emit('click')
    }
  }
}

// Vue 3 Composition API
interface Props {
  variant?: string
}

const props = withDefaults(defineProps<Props>(), {
  variant: 'primary'
})

const isLoading = ref(false)

const emit = defineEmits<{
  click: []
}>()

const handleClick = () => {
  emit('click')
}

From Options API to Composition API

vue
<!-- Options API -->
<template>
  <div>
    <input v-model="searchQuery" @input="handleSearch" />
    <div v-if="isLoading">Loading...</div>
    <div v-for="result in results" :key="result.id">
      {{ result.title }}
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      searchQuery: '',
      results: [],
      isLoading: false
    }
  },
  methods: {
    async handleSearch() {
      this.isLoading = true
      try {
        this.results = await this.$api.search(this.searchQuery)
      } finally {
        this.isLoading = false
      }
    }
  }
}
</script>

<!-- Composition API -->
<template>
  <div>
    <input v-model="searchQuery" @input="handleSearch" />
    <div v-if="isLoading">Loading...</div>
    <div v-for="result in results" :key="result.id">
      {{ result.title }}
    </div>
  </div>
</template>

<script setup>
const searchQuery = ref('')
const results = ref([])
const isLoading = ref(false)

const handleSearch = async () => {
  isLoading.value = true
  try {
    results.value = await $fetch(`/api/search?q=${searchQuery.value}`)
  } finally {
    isLoading.value = false
  }
}
</script>

Troubleshooting

Common Issues

Component not rendering:

typescript
// Check if component is properly registered
console.log('Available components:', Object.keys(components))

// Check props
console.log('Component props:', props)

Styling issues:

css
/* Check CSS specificity */
.component {
  /* Your styles */
}

/* Override with higher specificity if needed */
.app .component {
  /* Override styles */
}

Performance issues:

typescript
// Use React.memo equivalent
const OptimizedComponent = memo(Component, (prevProps, nextProps) => {
  return prevProps.id === nextProps.id
})

// Or use computed for expensive calculations
const expensiveValue = computed(() => {
  return heavyCalculation(props.data)
})

Contributing

Adding New Components

  1. Create component file:
bash
mkdir -p components/ui
touch components/ui/NewComponent.vue
  1. Follow naming conventions:
typescript
// components/ui/NewComponent.vue
interface Props {
  // Define props
}

const props = withDefaults(defineProps<Props>(), {
  // Default values
})

defineEmits<{
  // Define events
}>()
  1. Add tests:
typescript
// test/components/ui/NewComponent.test.ts
describe('NewComponent', () => {
  test('renders correctly', () => {
    // Test implementation
  })
})
  1. Update documentation:
markdown
## NewComponent

Description of the new component.

### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| prop1 | string | '' | Description |

### Events

| Event | Payload | Description |
|-------|---------|-------------|
| event1 | Event | Description |

### Usage

\`\`\`vue
<template>
  <NewComponent prop1="value" @event1="handler" />
</template>
\`\`\`

Resources


Next: Check out the Examples Section to see components in action!

Released under the MIT License.