# Styling Guide

How to style Grimoire components, create themed wrappers, and use CSS variables.

---

## 🎨 Core Concept: Headless Components

Grimoire components are **intentionally unstyled** (headless). This means:

- ✅ **No default styling** - You have complete control over appearance
- ✅ **Flexibility** - Works with any design system
- ✅ **Small bundle size** - No CSS shipped with the library
- ✅ **Framework agnostic** - Use Tailwind, SCSS, CSS Modules, etc.

**To use Grimoire in your project, create StyledComponents that wrap the base components.**

---

## 🎯 Creating Styled Components

### The `useStyledComponent` Composable

v3 introduces a powerful composable for creating styled wrappers:

```vue
<!-- StyledButton.vue -->
<template>
  <GButton ref="buttonRef" v-bind="mergedProps" class="styled-button">
    <slot />
  </GButton>
</template>

<script setup>
import { ref } from 'vue';
import { GButton } from '@twentyfourg/grimoire';
import { useStyledComponent } from '@twentyfourg/grimoire/composables';

const props = defineProps({
  ...GButton.props,
  variant: {
    type: String,
    default: 'primary filled',
  },
});

const buttonRef = ref(null);

const { mergedProps, expose } = useStyledComponent(buttonRef, props, {
  defaults: {
    rounded: true, // Project-wide default
  },
});

defineExpose(expose);
</script>

<style lang="scss" scoped>
.styled-button {
  /* Your custom styles */
  padding: 0.5rem 1rem;
  background: var(--primary-color);
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;

  &:hover {
    background: var(--primary-color-dark);
  }

  &.rounded {
    border-radius: 24px;
  }
}
</style>
```

### What `useStyledComponent` Does

1. **Merges props** - Combines defaults with user props
2. **Forwards methods** - All base component methods automatically available
3. **Smart defaults** - Conditional props based on other props
4. **Type safety** - Full TypeScript support

---

## 🎨 Styling Approaches

### Approach 1: SCSS/SASS (Recommended)

```vue
<style lang="scss" scoped>
.styled-input {
  position: relative;

  input {
    width: 100%;
    padding: 0.75rem;
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius);

    &:focus {
      outline: none;
      border-color: var(--primary-color);
    }

    &.error {
      border-color: var(--error-color);
    }
  }
}
</style>
```

### Approach 2: Tailwind CSS

```vue
<template>
  <GButton v-bind="$attrs" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50">
    <slot />
  </GButton>
</template>

<script setup>
import { GButton } from '@twentyfourg/grimoire';

defineOptions({
  inheritAttrs: false,
});
</script>
```

### Approach 3: CSS Modules

```vue
<template>
  <GButton v-bind="$attrs" :class="$style.styledButton">
    <slot />
  </GButton>
</template>

<script setup>
import { GButton } from '@twentyfourg/grimoire';
</script>

<style module>
.styledButton {
  padding: 0.5rem 1rem;
  background: #007bff;
  color: white;
  border-radius: 4px;
}

.styledButton:hover {
  background: #0056b3;
}
</style>
```

---

## 🎨 CSS Variables

Use CSS variables for themeable styles:

### Defining Variables

```scss
// variables.scss
:root {
  // Colors
  --primary-color: #007bff;
  --secondary-color: #6c757d;
  --danger-color: #dc3545;
  --success-color: #28a745;

  // Spacing
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;

  // Border radius
  --border-radius: 4px;
  --border-radius-lg: 8px;

  // Transitions
  --transition-speed: 200ms;
}
```

### Using in Styled Components

```vue
<style lang="scss" scoped>
.styled-button {
  padding: var(--spacing-md) var(--spacing-lg);
  background: var(--primary-color);
  border-radius: var(--border-radius);
  transition: all var(--transition-speed);

  &:hover {
    transform: translateY(-2px);
  }
}
</style>
```

### Component-Specific Variables

```vue
<template>
  <GButton ref="buttonRef" v-bind="mergedProps" class="styled-button" :style="buttonStyles">
    <slot />
  </GButton>
</template>

<script setup>
import { computed } from 'vue';

const props = defineProps({
  // ... props
  primaryColor: {
    type: String,
    default: '#007bff',
  },
});

const buttonStyles = computed(() => ({
  '--button-bg': props.primaryColor,
  '--button-hover-bg': darkenColor(props.primaryColor, 10),
}));
</script>

<style scoped>
.styled-button {
  background: var(--button-bg);
}

.styled-button:hover {
  background: var(--button-hover-bg);
}
</style>
```

---

## 🧩 Complete Styled Component Examples

### StyledInput.vue

```vue
<template>
  <GInput ref="inputRef" v-bind="mergedProps" class="styled-input" :class="variant">
    <template v-for="(_, name) in $slots" v-slot:[name]="slotData">
      <slot :name="name" v-bind="slotData" />
    </template>
  </GInput>
</template>

<script setup>
import { ref } from 'vue';
import { GInput } from '@twentyfourg/grimoire';
import { useStyledComponent } from '@twentyfourg/grimoire/composables';

const props = defineProps({
  ...GInput.props,
  variant: {
    type: String,
    default: 'outlined',
    validator: (v) => ['filled', 'outlined', 'lined'].includes(v),
  },
});

const inputRef = ref(null);

const { mergedProps, expose } = useStyledComponent(inputRef, props, {
  defaults: {
    clearable: true,
  },
});

defineExpose(expose);

defineOptions({
  inheritAttrs: false,
});
</script>

<style lang="scss" scoped>
.styled-input {
  width: 100%;

  input,
  textarea {
    width: 100%;
    padding: 0.75rem;
    font-size: 1rem;
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius);
    transition: all var(--transition-speed);

    &:focus {
      outline: none;
      border-color: var(--primary-color);
      box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
    }

    &::placeholder {
      color: var(--text-muted);
    }
  }

  &.filled input,
  &.filled textarea {
    background: var(--bg-gray-light);
    border-color: transparent;
  }

  &.lined input,
  &.lined textarea {
    border: none;
    border-bottom: 2px solid var(--border-color);
    border-radius: 0;
    padding-left: 0;
    padding-right: 0;
  }

  &.error input,
  &.error textarea {
    border-color: var(--danger-color);
  }
}
</style>
```

### StyledDropdown.vue

```vue
<template>
  <GDropdown ref="dropdownRef" v-bind="mergedProps" class="styled-dropdown" :class="variant">
    <!-- Custom caret icon -->
    <template #caret>
      <GIcon icon="app:chevron-down" class="dropdown-caret" />
    </template>

    <!-- Forward all other slots -->
    <template v-for="(_, name) in $slots" v-slot:[name]="slotData">
      <slot :name="name" v-bind="slotData" />
    </template>
  </GDropdown>
</template>

<script setup>
import { ref } from 'vue';
import { GDropdown, GIcon } from '@twentyfourg/grimoire';
import { useStyledComponent } from '@twentyfourg/grimoire/composables';

const props = defineProps({
  ...GDropdown.props,
  variant: {
    type: String,
    default: 'default',
  },
});

const dropdownRef = ref(null);

const { mergedProps, expose } = useStyledComponent(dropdownRef, props, {
  defaults: {
    canClear: false,
    canDeselect: false,
  },
  computeProps(merged, attrs) {
    // Auto-configure multi-select modes
    const mode = attrs.mode || merged.mode;
    if (mode === 'tags' || mode === 'multiple') {
      merged.hideSelected = merged.hideSelected ?? false;
      merged.closeOnSelect = merged.closeOnSelect ?? false;
    }
    return merged;
  },
});

defineExpose(expose);

defineOptions({
  inheritAttrs: false,
});
</script>

<style lang="scss" scoped>
.styled-dropdown {
  // Style the multiselect wrapper
  :deep(.multiselect) {
    min-height: 42px;
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius);

    &:focus-within {
      border-color: var(--primary-color);
      box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
    }
  }

  :deep(.multiselect-input) {
    padding: 0.5rem;
  }

  :deep(.multiselect-dropdown) {
    border-radius: var(--border-radius);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  }

  :deep(.multiselect-option) {
    padding: 0.5rem 0.75rem;

    &:hover {
      background: var(--bg-gray-light);
    }

    &.is-selected {
      background: var(--primary-color);
      color: white;
    }
  }

  .dropdown-caret {
    transition: transform var(--transition-speed);
  }

  &.is-open .dropdown-caret {
    transform: rotate(180deg);
  }
}
</style>
```

---

## 🎨 Theming

### Theme Structure

```
theme/
├── variables.scss          # CSS variables
├── StyledButton.vue       # Styled components
├── StyledInput.vue
├── StyledDropdown.vue
├── StyledModal.vue
└── index.ts               # Export all styled components
```

### Theme Index

```typescript
// theme/index.ts
export { default as StyledButton } from './StyledButton.vue';
export { default as StyledInput } from './StyledInput.vue';
export { default as StyledDropdown } from './StyledDropdown.vue';
// ... export all styled components
```

### Using Themed Components

```vue
<template>
  <StyledButton @click="submit">Submit</StyledButton>
  <StyledInput v-model="email" type="email" />
  <StyledDropdown v-model="role" :options="roles" />
</template>

<script setup>
import { ref } from 'vue';
import { StyledButton, StyledInput, StyledDropdown } from '@/theme';

const email = ref('');
const role = ref(null);
const roles = ref(['Admin', 'User', 'Guest']);

function submit() {
  // Submit logic
}
</script>
```

---

## 🎯 Best Practices

### 1. Use CSS Variables for Themeable Values

```scss
// ✅ Good - themeable
.styled-button {
  background: var(--primary-color);
  padding: var(--spacing-md);
}

// ❌ Bad - hardcoded
.styled-button {
  background: #007bff;
  padding: 1rem;
}
```

### 2. Scope Styles Properly

```vue
<style scoped>
/* Scoped styles won't leak */
.styled-button {
  /* ... */
}
</style>
```

### 3. Use `:deep()` for Child Styling

```scss
// Style deeply nested elements
.styled-dropdown {
  :deep(.multiselect-option) {
    padding: 0.5rem;
  }
}
```

### 4. Support Dark Mode

```scss
.styled-button {
  background: var(--button-bg);
  color: var(--button-text);
}

:root {
  --button-bg: #007bff;
  --button-text: white;
}

:root[data-theme='dark'] {
  --button-bg: #0056b3;
  --button-text: #e0e0e0;
}
```

### 5. Maintain Consistency

Create a design system:

```scss
// design-system.scss
:root {
  // Colors
  --color-primary: #007bff;
  --color-secondary: #6c757d;

  // Spacing scale
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;

  // Border radius
  --radius-sm: 2px;
  --radius-md: 4px;
  --radius-lg: 8px;

  // Typography
  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.25rem;
}
```

---

## 🎨 Advanced Styling Techniques

### Dynamic Classes

```vue
<template>
  <GButton
    v-bind="$attrs"
    class="styled-button"
    :class="{
      'is-loading': loading,
      'is-disabled': disabled,
      [`variant-${variant}`]: variant,
    }"
  >
    <slot />
  </GButton>
</template>

<script setup>
defineProps({
  variant: String,
  loading: Boolean,
  disabled: Boolean,
});
</script>
```

### Computed Styles

```vue
<template>
  <GButton :style="computedStyles">
    <slot />
  </GButton>
</template>

<script setup>
import { computed } from 'vue';

const props = defineProps({
  color: String,
  size: String,
});

const computedStyles = computed(() => ({
  '--button-color': props.color,
  '--button-size': props.size === 'lg' ? '3rem' : '2rem',
}));
</script>
```

### Style Variants

```vue
<style lang="scss" scoped>
.styled-button {
  // Base styles
  padding: var(--spacing-md);
  border-radius: var(--border-radius);

  // Size variants
  &.size-sm {
    padding: var(--spacing-sm);
  }
  &.size-lg {
    padding: var(--spacing-lg);
  }

  // Color variants
  &.variant-primary {
    background: var(--primary-color);
  }
  &.variant-danger {
    background: var(--danger-color);
  }
  &.variant-success {
    background: var(--success-color);
  }

  // State variants
  &.is-loading {
    opacity: 0.6;
    pointer-events: none;
  }
  &.is-disabled {
    opacity: 0.4;
    cursor: not-allowed;
  }
}
</style>
```

---

## 📚 Related Documentation

- **[Component Usage](./component-usage.md)** - How to use components
- **[TypeScript Usage](./typescript-usage.md)** - TypeScript patterns
- **[Migration Guide](./v2-to-v3-migration-guide.md)** - Upgrading from v2

---

## 💡 Tips

- **Start simple** - Create basic styled components first, then add complexity
- **Reuse patterns** - Use the same structure across all styled components
- **Test themes** - Verify styled components work with different themes
- **Document variants** - Keep a style guide for your styled components

---

**For real examples, see the default theme in [`packages/themes/default/`](../../../../../../packages/themes/default/).**



