@shopkit/core
v0.1.0
Published
Core storefront utilities including theme and context
Maintainers
Readme
@shopkit/core
Core storefront utilities including theme management and authentication context.
Features
- Theme System: Complete theming with CSS variables, React context, and SSR support
- CSS Variable Generation: Automatic CSS custom property generation from theme config
- Theme Registry: Caching and loading of merchant themes
- Auth Context: Customer authentication state management
- Server/Client Separation: Next.js App Router compatible architecture
Installation
npm install @shopkit/core
# or
bun add @shopkit/corePeer Dependencies
npm install react react-dom nextQuick Start
Theme Setup (Next.js App Router)
// app/layout.tsx (Server Component)
import { getActiveTheme } from '@shopkit/core/theme/server';
import { ThemeProvider } from '@shopkit/core/theme/client';
export default async function RootLayout({ children }) {
const theme = await getActiveTheme('my-merchant');
return (
<html>
<body>
<ThemeProvider merchantName="my-merchant" initialTheme={theme}>
{children}
</ThemeProvider>
</body>
</html>
);
}// components/MyComponent.tsx (Client Component)
'use client';
import { useTheme } from '@shopkit/core/theme/client';
export function MyComponent() {
const { theme, getValue } = useTheme();
const primaryColor = getValue('colors.primary', '#000000');
return <div style={{ color: primaryColor }}>Themed content</div>;
}Theme System
ThemeConfig Structure
import type { ThemeConfig } from '@shopkit/core';
const themeConfig: ThemeConfig = {
colors: {
primary: '#1a1a1a',
secondary: '#666666',
accent: '#0066cc',
background: '#ffffff',
text: '#1a1a1a',
},
typography: {
fontFamily: {
heading: '"Inter", sans-serif',
body: '"Inter", sans-serif',
},
fontSize: {
xs: '0.75rem',
sm: '0.875rem',
base: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
'4xl': '2.25rem',
},
fontWeight: {
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
},
},
spacing: {
xs: '0.25rem',
sm: '0.5rem',
md: '1rem',
lg: '1.5rem',
xl: '2rem',
'2xl': '3rem',
},
borderRadius: {
none: '0',
sm: '0.25rem',
md: '0.375rem',
lg: '0.5rem',
full: '9999px',
},
};CSS Variable Generation
import { generateThemeCSS, getCSSVariableMap } from '@shopkit/core/theme';
// Generate CSS string
const css = generateThemeCSS(themeConfig);
// Output: :root { --colors-primary: #1a1a1a; --typography-font-family-heading: "Inter", sans-serif; ... }
// Get variable map
const varMap = getCSSVariableMap(themeConfig);
// { 'colors.primary': '--colors-primary', 'typography.fontFamily.heading': '--typography-font-family-heading', ... }Theme Registry
import { createThemeRegistry } from '@shopkit/core/theme';
const registry = createThemeRegistry({
basePath: './src/themes',
});
// Load theme for a merchant
const theme = await registry.loadTheme('my-merchant', 'light');useTheme Hook
import { useTheme } from '@shopkit/core/theme/client';
function MyComponent() {
const {
theme, // Current theme object
getValue, // Get a theme value by path
getCSSVar, // Get CSS variable reference
merchantName, // Current merchant name
} = useTheme();
// Get value with fallback
const color = getValue('colors.accent', '#default');
// Get CSS variable reference
const cssVar = getCSSVar('colors.primary');
// Returns: 'var(--colors-primary)'
}Auth Context
Setup
// app/layout.tsx
import { AuthProvider } from '@shopkit/core/context';
export default function RootLayout({ children }) {
return (
<AuthProvider>
{children}
</AuthProvider>
);
}Usage
'use client';
import { useAuth } from '@shopkit/core/context';
function AccountButton() {
const { customer, isAuthenticated, login, logout } = useAuth();
if (isAuthenticated) {
return (
<div>
<span>Welcome, {customer.firstName}</span>
<button onClick={logout}>Logout</button>
</div>
);
}
return <button onClick={() => login()}>Login</button>;
}Subpath Exports
// Main entry (everything)
import { ThemeProvider, AuthProvider } from '@shopkit/core';
// Theme module
import { ThemeConfig, Theme } from '@shopkit/core/theme';
// Theme client (React components/hooks)
import { ThemeProvider, useTheme } from '@shopkit/core/theme/client';
// Theme server (SSR utilities)
import { getActiveTheme, configureThemeLoader } from '@shopkit/core/theme/server';
// Context module (Auth)
import { AuthProvider, useAuth } from '@shopkit/core/context';Type Reference
Theme Types
// Theme configuration (what you define)
interface ThemeConfig {
colors?: Record<string, string>;
typography?: {
fontFamily?: Record<string, string>;
fontSize?: Record<string, string>;
fontWeight?: Record<string, string>;
lineHeight?: Record<string, string>;
};
spacing?: Record<string, string>;
borderRadius?: Record<string, string>;
[key: string]: unknown;
}
// Resolved theme (what you use)
interface Theme {
id: string;
name: string;
role: ThemeRole;
config: ThemeConfig;
}
// Theme roles
type ThemeRole = 'light' | 'dark' | 'high-contrast';ThemeContextValue
interface ThemeContextValue {
theme: Theme | null;
merchantName: string;
isLoading: boolean;
getValue: <T>(path: string, defaultValue?: T) => T;
getCSSVar: (path: string) => string;
}Utility Functions
Path to CSS Variable
import { pathToCssVar, cssVarRef } from '@shopkit/core/theme';
pathToCssVar('colors.primary'); // '--colors-primary'
cssVarRef('colors.primary'); // 'var(--colors-primary)'Object Flattening
import { flattenObject, unflattenObject } from '@shopkit/core/theme';
const flat = flattenObject({ colors: { primary: '#000' } });
// { 'colors.primary': '#000' }
const nested = unflattenObject({ 'colors.primary': '#000' });
// { colors: { primary: '#000' } }Value Resolution
import { resolveThemeValue, hasThemeValue } from '@shopkit/core/theme';
const theme = { config: { colors: { primary: '#000' } } };
resolveThemeValue(theme, 'colors.primary'); // '#000'
hasThemeValue(theme, 'colors.primary'); // true
hasThemeValue(theme, 'colors.nonexistent'); // falseIntegration with @shopkit/builder
import { createPageBuilder } from '@shopkit/builder';
import { createThemeRegistry } from '@shopkit/core/theme';
const themeRegistry = createThemeRegistry({
basePath: './src/themes',
});
const pageBuilder = createPageBuilder({
widgets: widgetRegistry,
commerceClient: shopifyClient,
themeLoader: themeRegistry, // Implements IThemeLoader
templateLoader: templateLoader,
});License
MIT
