JSPM

  • Created
  • Published
  • Downloads 9
  • Score
    100M100P100Q93102F
  • License MIT

An implementation to control the schema associated with "color".

Package Exports

  • @fastkit/color-scheme
  • @fastkit/color-scheme/color-scheme.d.ts
  • @fastkit/color-scheme/color-scheme.mjs
  • @fastkit/color-scheme/color-scheme.mjs.map
  • @fastkit/color-scheme/package.json
  • @fastkit/color-scheme/plugboy-dts-preserve
  • @fastkit/color-scheme/plugboy-dts-preserve.d.ts
  • @fastkit/color-scheme/plugboy-dts-preserve.mjs
  • @fastkit/color-scheme/plugboy-dts-preserve.mjs.map

Readme

@fastkit/color-scheme

🌐 English | 日本語

A comprehensive color system library for managing application color themes and schemes. Built with TypeScript, it provides a color management system that emphasizes type safety, dynamic theme switching, and accessibility support.

Features

  • Strict Type Safety: Complete type checking through TypeScript generics
  • Dynamic Theme Switching: Automatic detection and switching of light/dark modes
  • Flexible Palette Management: Function-based dynamic color generation
  • Complete Vue.js Integration: Dedicated composables and directives
  • Automatic CSS Variables Generation: Efficient CSS custom property management
  • Accessibility Support: Automatic contrast calculation and focus management
  • Context-Aware: Automatic color adjustment based on background
  • Extensible Design: Easy addition of custom themes and scopes
  • Performance Optimization: Lazy evaluation and caching mechanisms
  • Build Tool Integration: Complete integration with Plugboy

Installation

npm install @fastkit/color-scheme
# or
pnpm add @fastkit/color-scheme

# If Vue.js integration is needed
npm install @fastkit/vue-color-scheme

Basic Usage

Creating a Simple Color Scheme

import { createColorScheme } from '@fastkit/color-scheme';

const colorScheme = createColorScheme({
  // Color variant definitions
  variants: ['contained', 'outlined', 'inverted', 'plain'],

  // Optional color definitions
  optionals: ['light', 'deep', 'text', 'border', 'focus'],

  // Theme definitions
  themes: [
    {
      name: 'light',
      palette: [
        ['primary', '#1976d2'],
        ['secondary', '#424242'],
        ['success', '#4caf50'],
        ['warning', '#ff9800'],
        ['error', '#f44336'],
        ['background', '#ffffff'],
        ['surface', '#f5f5f5']
      ],
      scopes: [
        ['primary', ({ palette }) => palette('primary')],
        ['secondary', ({ palette }) => palette('secondary')],
        ['success', ({ palette }) => palette('success')]
      ]
    },
    {
      name: 'dark',
      palette: [
        ['primary', '#42a5f5'],
        ['secondary', '#616161'],
        ['background', '#121212'],
        ['surface', '#1e1e1e']
      ],
      // Undefined scopes inherit from light theme
    }
  ]
});

Using with Vue.js

<template>
  <div :class="themeClass">
    <!-- Primary color button -->
    <button :class="primaryClasses.contained">
      Primary Button
    </button>

    <!-- Secondary color outline button -->
    <button :class="secondaryClasses.outlined">
      Secondary Button
    </button>

    <!-- Theme toggle button -->
    <button @click="toggleTheme">
      Switch to {{ currentTheme === 'dark' ? 'Light' : 'Dark' }} theme
    </button>
  </div>
</template>

<script setup lang="ts">
import { useColorScheme, useColorClasses, useThemeClass } from '@fastkit/vue-color-scheme';

// Color scheme service
const colorScheme = useColorScheme();

// Theme class management
const { themeClass, currentTheme, toggleTheme } = useThemeClass();

// Color class generation
const primaryClasses = useColorClasses('primary');
const secondaryClasses = useColorClasses('secondary');
</script>

Color Scheme Definition

Palette Definition

// Static color definition
palette: [
  ['primary', '#1976d2'],
  ['secondary', '#424242']
]

// Dynamic color definition
palette: [
  ['primary', '#1976d2'],
  ['primaryLight', ({ palette }) => palette('primary').lighten(0.2)],
  ['primaryDark', ({ palette }) => palette('primary').darken(0.2)],
  ['onPrimary', ({ palette }) => {
    // Automatically select text color based on background brightness
    const bg = palette('primary');
    return bg.brightness() > 0.5 ? '#000000' : '#ffffff';
  }]
]

Scope Definition

scopes: [
  // Basic scope
  ['primary', ({ palette }) => palette('primary')],

  // Context-aware scope
  ['error', ({ palette, theme }) => {
    const base = palette('error');
    // Adjust slightly brighter in dark theme
    return theme.isDark ? base.lighten(0.1) : base;
  }],

  // Conditional scope
  ['accent', ({ palette, theme }) => {
    return theme.name === 'dark' ? palette('secondary') : palette('primary');
  }]
]

Scope Defaults

scopeDefaults: ({ palette, theme }) => ({
  // Automatic default scope generation
  default: [
    'transparent',
    {
      text: theme.isDark ? '#ffffff' : '#000000',
      border: theme.isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.12)',
      focus: palette('primary').alpha(0.12),
      focusShadow: palette('primary').alpha(0.3)
    }
  ],

  // Automatic variation generation for each color
  primary: [
    ({ palette }) => palette('primary'),
    {
      light: ({ main }) => main.alpha(0.04),
      deep: ({ main }) => main.alpha(0.1),
      text: ({ main }) => main.brightness() > 0.6 ? '#000000' : '#ffffff',
      border: ({ main }) => main.alpha(0.5),
      focus: ({ main }) => main.darken(0.07),
      focusShadow: ({ main }) => main.alpha(0.5)
    }
  ]
})

Theme Management

Automatic Light/Dark Detection

const scheme = createColorScheme({
  themes: [
    {
      name: 'light',
      // Automatically detected as light theme with brightness >= 0.5
      palette: [['background', '#ffffff']]
    },
    {
      name: 'dark',
      // Automatically detected as dark theme with brightness < 0.5
      palette: [['background', '#121212']]
    }
  ]
});

// Get theme detection results
console.log(scheme.theme('light').isLight); // true
console.log(scheme.theme('dark').isDark);   // true

Dynamic Theme Switching

// Vue.js composable usage example
const {
  currentTheme,     // Current theme name
  setTheme,         // Theme setting
  toggleTheme,      // Theme switching
  isDark,          // Whether it's dark theme
  isLight          // Whether it's light theme
} = useThemeClass();

// Programmatic theme switching
setTheme('dark');

// Follow system settings
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
setTheme(prefersDark.matches ? 'dark' : 'light');

// Monitor automatic switching
prefersDark.addEventListener('change', (e) => {
  setTheme(e.matches ? 'dark' : 'light');
});

Color Variants

Built-in Variants

const variants = ['contained', 'outlined', 'inverted', 'plain'];

// CSS class examples:
// - primary-contained: Filled style
// - primary-outlined: Outline style
// - primary-inverted: Inverted style
// - primary-plain: Plain style

Custom Variant Definition

const colorScheme = createColorScheme({
  variants: ['contained', 'outlined', 'inverted', 'plain', 'gradient', 'shadow'],

  // CSS definitions for custom variants need to be provided separately
  themes: [/* ... */]
});

Advanced Usage Examples

Responsive Color System

const responsiveColorScheme = createColorScheme({
  variants: ['contained', 'outlined', 'text'],
  optionals: ['light', 'deep', 'hover', 'active', 'disabled'],

  themes: [
    {
      name: 'light',
      palette: [
        ['primary', '#1976d2'],
        ['secondary', '#424242'],
        ['background', '#ffffff'],
        ['surface', '#f5f5f5'],

        // Responsive support
        ['mobile-primary', ({ palette }) => palette('primary').saturate(0.1)],
        ['tablet-primary', ({ palette }) => palette('primary')],
        ['desktop-primary', ({ palette }) => palette('primary').desaturate(0.05)]
      ],

      scopes: [
        ['primary', ({ palette }) => {
          // Color selection based on media queries
          const isMobile = window.innerWidth < 768;
          const isTablet = window.innerWidth < 1024;

          if (isMobile) return palette('mobile-primary');
          if (isTablet) return palette('tablet-primary');
          return palette('desktop-primary');
        }]
      ]
    }
  ]
});

Animation-Compatible Color System

const animatedColorScheme = createColorScheme({
  variants: ['contained', 'outlined'],
  optionals: ['hover', 'active', 'focus', 'disabled'],

  themes: [
    {
      name: 'light',
      palette: [
        ['primary', '#1976d2'],
        ['primary-hover', ({ palette }) => palette('primary').lighten(0.08)],
        ['primary-active', ({ palette }) => palette('primary').darken(0.12)],
        ['primary-focus', ({ palette }) => palette('primary').alpha(0.12)],
        ['primary-disabled', ({ palette }) => palette('primary').alpha(0.38)]
      ],

      scopes: [
        ['primary', ({ palette }) => palette('primary')],
        ['primary-interactive', ({ palette }) => ({
          default: palette('primary'),
          hover: palette('primary-hover'),
          active: palette('primary-active'),
          focus: palette('primary-focus'),
          disabled: palette('primary-disabled')
        })]
      ]
    }
  ]
});

Component Library Integration

<template>
  <div class="button-component" :class="buttonClasses">
    <slot />
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import { useColorClasses } from '@fastkit/vue-color-scheme';

interface Props {
  color?: 'primary' | 'secondary' | 'success' | 'warning' | 'error';
  variant?: 'contained' | 'outlined' | 'text';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
  color: 'primary',
  variant: 'contained',
  size: 'md',
  disabled: false
});

// Color class generation
const colorClasses = useColorClasses(props.color);

// Final class name calculation
const buttonClasses = computed(() => [
  'button',
  `button--${props.size}`,
  colorClasses[props.variant],
  {
    'button--disabled': props.disabled
  }
]);
</script>

<style scoped>
.button {
  /* Base styles */
  padding: var(--spacing-md);
  border-radius: var(--border-radius);
  font-weight: 500;
  transition: all 0.2s ease;
  cursor: pointer;
}

.button--sm { padding: var(--spacing-sm); }
.button--md { padding: var(--spacing-md); }
.button--lg { padding: var(--spacing-lg); }

.button--disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* Use CSS variables from color scheme */
.button.primary-contained {
  background-color: var(--color-primary);
  color: var(--color-primary-text);
  border: 1px solid var(--color-primary);
}

.button.primary-contained:hover {
  background-color: var(--color-primary-deep);
}

.button.primary-outlined {
  background-color: transparent;
  color: var(--color-primary);
  border: 1px solid var(--color-primary);
}
</style>

CSS Variables Integration

Auto-generated CSS Variables

:root.light-theme {
  /* Primary colors */
  --color-primary: #1976d2;
  --color-primary-light: rgba(25, 118, 210, 0.04);
  --color-primary-deep: rgba(25, 118, 210, 0.1);
  --color-primary-text: #ffffff;
  --color-primary-border: rgba(25, 118, 210, 0.5);
  --color-primary-focus: #1565c0;
  --color-primary-focus-shadow: rgba(25, 118, 210, 0.5);

  /* Secondary colors */
  --color-secondary: #424242;
  --color-secondary-light: rgba(66, 66, 66, 0.04);
  --color-secondary-deep: rgba(66, 66, 66, 0.1);
  --color-secondary-text: #ffffff;

  /* System colors */
  --color-background: #ffffff;
  --color-surface: #f5f5f5;
  --color-on-background: #000000;
  --color-on-surface: #000000;
}

:root.dark-theme {
  --color-primary: #42a5f5;
  --color-secondary: #616161;
  --color-background: #121212;
  --color-surface: #1e1e1e;
  --color-on-background: #ffffff;
  --color-on-surface: #ffffff;
}

SCSS Integration

// _variables.scss (auto-generated)
$color-primary: var(--color-primary);
$color-primary-light: var(--color-primary-light);
$color-primary-deep: var(--color-primary-deep);

// Component styles
.my-component {
  background-color: $color-primary;
  color: $color-primary-text;

  &:hover {
    background-color: $color-primary-deep;
  }

  &:focus {
    box-shadow: 0 0 0 2px $color-primary-focus-shadow;
  }
}

Plugboy Integration

Plugin Configuration

// plugboy.workspace.ts
import { defineWorkspaceConfig } from '@fastkit/plugboy';
import { colorSchemePlugin } from '@fastkit/color-scheme-gen';

export default defineWorkspaceConfig({
  entries: {
    '.': './src/index.ts'
  },
  plugins: [
    colorSchemePlugin({
      // Color scheme definition file
      input: './src/color-scheme.ts',
      // CSS output path
      output: './dist/colors.css',
      // SCSS variable output
      scssOutput: './src/styles/_colors.scss',
      // Additional settings
      generateCssVars: true,
      generateScssVars: true,
      minify: true
    })
  ]
});

Build-time Color Generation

// src/color-scheme.ts
import { createSimpleColorScheme } from '@fastkit/color-scheme-gen';

export const colorScheme = createSimpleColorScheme({
  themes: {
    light: {
      primary: '#1976d2',
      secondary: '#424242',
      success: '#4caf50',
      warning: '#ff9800',
      error: '#f44336',
      background: '#ffffff',
      surface: '#f5f5f5'
    },
    dark: {
      primary: '#42a5f5',
      secondary: '#616161',
      success: '#66bb6a',
      warning: '#ffa726',
      error: '#ef5350',
      background: '#121212',
      surface: '#1e1e1e'
    }
  }
});

Accessibility Support

Automatic Contrast Calculation

const accessibleColorScheme = createColorScheme({
  themes: [
    {
      name: 'light',
      palette: [
        ['primary', '#1976d2'],
        // Automatically select text color based on background brightness
        ['primary-text', ({ palette }) => {
          const bg = palette('primary');
          const brightness = bg.brightness();

          // Ensure contrast based on WCAG AA standards
          if (brightness > 0.5) {
            return '#000000'; // Black text for bright backgrounds
          } else {
            return '#ffffff'; // White text for dark backgrounds
          }
        }]
      ]
    }
  ]
});

Focus Management

scopeDefaults: ({ palette, theme }) => ({
  primary: [
    ({ palette }) => palette('primary'),
    {
      // Ensure focus visibility
      focus: ({ main }) => main.darken(0.1),
      focusShadow: ({ main }) => main.alpha(0.3),

      // Keyboard navigation support
      focusVisible: ({ main }) => main.alpha(0.12),
      focusRing: ({ main }) => main.alpha(0.5)
    }
  ]
})

Performance Optimization

Lazy Evaluation

// Color values are not calculated until needed
const lazyColor = ({ palette }) => palette('primary').lighten(0.2);

// Calculated only when accessed
const actualColor = scheme.scope('primary').light;

Caching Mechanism

// Once calculated values are cached
const cachedScheme = createColorScheme({
  cache: true, // Enable caching
  themes: [/* ... */]
});

// Re-access to same theme/scope is fast
const color1 = cachedScheme.scope('primary').main;
const color2 = cachedScheme.scope('primary').main; // Retrieved from cache

Type Safety and Extension

Module Extension

// types/color-scheme.d.ts
declare module '@fastkit/color-scheme' {
  interface ThemeSettings {
    light: 'light';
    dark: 'dark';
    auto: 'auto';
  }

  interface PaletteSettings {
    primary: 'primary';
    secondary: 'secondary';
    tertiary: 'tertiary';
    success: 'success';
    warning: 'warning';
    error: 'error';
    info: 'info';
  }

  interface ScopeSettings {
    primary: 'primary';
    secondary: 'secondary';
    surface: 'surface';
    background: 'background';
  }

  interface VariantSettings {
    contained: 'contained';
    outlined: 'outlined';
    text: 'text';
    elevated: 'elevated';
  }
}

Type-safe Color Access

// Type-safe scheme access
const color: string = scheme.scope('primary').main; // ✓ Valid
const invalid = scheme.scope('invalid'); // ✗ TypeScript error

// Type-safe theme access
const lightTheme = scheme.theme('light'); // ✓ Valid
const invalidTheme = scheme.theme('invalid'); // ✗ TypeScript error

Testing and Debugging

Unit Test Examples

import { describe, test, expect } from 'vitest';
import { createColorScheme } from '@fastkit/color-scheme';

describe('ColorScheme', () => {
  const scheme = createColorScheme({
    variants: ['contained', 'outlined'],
    themes: [
      {
        name: 'light',
        palette: [['primary', '#1976d2']],
        scopes: [['primary', ({ palette }) => palette('primary')]]
      }
    ]
  });

  test('theme access', () => {
    const theme = scheme.theme('light');
    expect(theme.name).toBe('light');
    expect(theme.isLight).toBe(true);
  });

  test('scope access', () => {
    const scope = scheme.scope('primary');
    expect(scope.main).toBe('#1976d2');
  });

  test('palette access', () => {
    const palette = scheme.theme('light').palette;
    expect(palette('primary')).toBe('#1976d2');
  });
});

Debug Features

// Enable debug mode
const debugScheme = createColorScheme({
  debug: true,
  themes: [/* ... */]
});

// Detailed color information output
console.log(debugScheme.debug.info());
// {
//   themes: ['light', 'dark'],
//   scopes: ['primary', 'secondary'],
//   variants: ['contained', 'outlined'],
//   optionals: ['light', 'deep', 'text']
// }

Dependencies

{
  "dependencies": {
    "@fastkit/color": "Color manipulation library",
    "@fastkit/tiny-logger": "Logging functionality"
  },
  "peerDependencies": {
    "vue": "^3.4.0"
  },
  "devDependencies": {
    "typescript": "^5.5.0",
    "vitest": "^1.0.0"
  }
}

Documentation

For detailed documentation, please visit here.

License

MIT