mirror of
https://github.com/umami-software/umami.git
synced 2026-02-10 23:57:12 +01:00
Add Niteshift Dials SDK for runtime design prototyping
Introduces a complete design dials system that allows designers and PMs to adjust UI parameters at runtime without code changes. **Dials SDK (`packages/dials/`):** - useDynamicColor: Color values with design system integration - useDynamicSpacing: Spacing/padding/margin controls - useDynamicVariant: Discrete choice selections - useDynamicBoolean: Toggle/feature flag controls - useDynamicNumber: Numeric values with min/max/step - DialsOverlay: Compact Leva-inspired UI (Ctrl+D to toggle) - DialsProvider: React context for dial state management - Design manifest integration for design system tokens **App Integration:** - Added DialsProvider to app Providers - Example dials on WebsitePage (metrics bar, panels, navigation) - MetricCard component with adjustable typography dials - TypeScript manifest at src/config/niteshift-manifest.ts **Documentation:** - Comprehensive CLAUDE.md section on dials usage - Best practices for preserving original appearance - Examples for all dial types 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f4d0a65b16
commit
2727fd6dff
39 changed files with 4623 additions and 19 deletions
36
packages/dials/src/hooks/useDial.ts
Normal file
36
packages/dials/src/hooks/useDial.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Core React hook for creating a dynamic dial
|
||||
* This is the base hook that all specific dial hooks use internally
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { getDialRegistry } from '../registry';
|
||||
import type { DialType, DialConfig } from '../types';
|
||||
|
||||
/**
|
||||
* Core hook for creating a dial
|
||||
* Registers the dial in the global registry and subscribes to changes
|
||||
*
|
||||
* @param id - Unique identifier for this dial
|
||||
* @param type - Type of dial
|
||||
* @param config - Configuration for the dial
|
||||
* @returns Current value of the dial
|
||||
*/
|
||||
export function useDial<T>(id: string, type: DialType, config: DialConfig): T {
|
||||
const registry = getDialRegistry();
|
||||
|
||||
// Register the dial and get initial value
|
||||
const [value, setValue] = useState<T>(() => registry.register<T>(id, type, config));
|
||||
|
||||
useEffect(() => {
|
||||
// Subscribe to changes for this specific dial
|
||||
const unsubscribe = registry.subscribe(id, (dialId, newValue) => {
|
||||
setValue(newValue);
|
||||
});
|
||||
|
||||
// Cleanup subscription on unmount
|
||||
return unsubscribe;
|
||||
}, [id, registry]);
|
||||
|
||||
return value;
|
||||
}
|
||||
26
packages/dials/src/hooks/useDynamicBoolean.ts
Normal file
26
packages/dials/src/hooks/useDynamicBoolean.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* React hook for creating a boolean dial (toggle)
|
||||
*/
|
||||
|
||||
import { useDial } from './useDial';
|
||||
import type { BooleanDialConfig } from '../types';
|
||||
|
||||
/**
|
||||
* Create a dynamic boolean dial for toggles and feature flags
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const showMetrics = useDynamicBoolean('show-metrics', {
|
||||
* label: 'Show Metrics',
|
||||
* default: true,
|
||||
* trueLabel: 'Visible',
|
||||
* falseLabel: 'Hidden',
|
||||
* group: 'Dashboard'
|
||||
* });
|
||||
*
|
||||
* {showMetrics && <MetricsPanel />}
|
||||
* ```
|
||||
*/
|
||||
export function useDynamicBoolean(id: string, config: Omit<BooleanDialConfig, 'type'>): boolean {
|
||||
return useDial<boolean>(id, 'boolean', { ...config, type: 'boolean' });
|
||||
}
|
||||
52
packages/dials/src/hooks/useDynamicColor.ts
Normal file
52
packages/dials/src/hooks/useDynamicColor.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* React hook for creating a color dial
|
||||
*/
|
||||
|
||||
import { useDial } from './useDial';
|
||||
import { useDialsContext } from '../components/DialsProvider';
|
||||
import type { ColorDialConfig } from '../types';
|
||||
|
||||
/**
|
||||
* Create a dynamic color dial
|
||||
*
|
||||
* When options are not provided, automatically pulls color values from the
|
||||
* design manifest (if available). Supports manifest categories like 'primary',
|
||||
* 'accent', 'semantic', etc.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // With explicit options:
|
||||
* const bgColor = useDynamicColor('hero-bg', {
|
||||
* label: 'Background Color',
|
||||
* default: '#1a1a1a',
|
||||
* options: ['#1a1a1a', '#2d2d2d', '#404040'],
|
||||
* group: 'Hero Section'
|
||||
* });
|
||||
*
|
||||
* // With manifest defaults (auto-populated from designManifest.colors.accent):
|
||||
* const accentColor = useDynamicColor('accent', {
|
||||
* label: 'Accent Color',
|
||||
* default: '#3e63dd',
|
||||
* manifestCategory: 'accent', // pulls from manifest
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function useDynamicColor(id: string, config: Omit<ColorDialConfig, 'type'>): string {
|
||||
const { manifest } = useDialsContext();
|
||||
|
||||
// Build config with optional manifest defaults
|
||||
const finalConfig: ColorDialConfig = { ...config, type: 'color' as const };
|
||||
|
||||
// If options not provided and we have a manifest, try to load from manifest
|
||||
if (!config.options && manifest?.colors) {
|
||||
const category = (config as any).manifestCategory || 'accent';
|
||||
const colorCategory = manifest.colors[category];
|
||||
|
||||
if (colorCategory?.values) {
|
||||
const values = colorCategory.values;
|
||||
finalConfig.options = Array.isArray(values) ? values : Object.values(values);
|
||||
}
|
||||
}
|
||||
|
||||
return useDial<string>(id, 'color', finalConfig);
|
||||
}
|
||||
28
packages/dials/src/hooks/useDynamicNumber.ts
Normal file
28
packages/dials/src/hooks/useDynamicNumber.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* React hook for creating a number dial
|
||||
*/
|
||||
|
||||
import { useDial } from './useDial';
|
||||
import type { NumberDialConfig } from '../types';
|
||||
|
||||
/**
|
||||
* Create a dynamic number dial
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const chartHeight = useDynamicNumber('chart-height', {
|
||||
* label: 'Chart Height',
|
||||
* default: 400,
|
||||
* min: 200,
|
||||
* max: 800,
|
||||
* step: 50,
|
||||
* unit: 'px',
|
||||
* group: 'Chart'
|
||||
* });
|
||||
*
|
||||
* <Chart height={chartHeight} />
|
||||
* ```
|
||||
*/
|
||||
export function useDynamicNumber(id: string, config: Omit<NumberDialConfig, 'type'>): number {
|
||||
return useDial<number>(id, 'number', { ...config, type: 'number' });
|
||||
}
|
||||
44
packages/dials/src/hooks/useDynamicSpacing.ts
Normal file
44
packages/dials/src/hooks/useDynamicSpacing.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* React hook for creating a spacing dial
|
||||
*/
|
||||
|
||||
import { useDial } from './useDial';
|
||||
import { useDialsContext } from '../components/DialsProvider';
|
||||
import type { SpacingDialConfig } from '../types';
|
||||
|
||||
/**
|
||||
* Create a dynamic spacing dial
|
||||
*
|
||||
* When options are not provided, automatically pulls spacing values from the
|
||||
* design manifest's spacing scale (if available).
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // With explicit options:
|
||||
* const padding = useDynamicSpacing('card-padding', {
|
||||
* label: 'Card Padding',
|
||||
* default: '1rem',
|
||||
* options: ['0.5rem', '1rem', '1.5rem', '2rem'],
|
||||
* group: 'Card'
|
||||
* });
|
||||
*
|
||||
* // With manifest defaults (auto-populated from designManifest.spacing):
|
||||
* const margin = useDynamicSpacing('section-margin', {
|
||||
* label: 'Section Margin',
|
||||
* default: '24px', // pulls options from manifest.spacing.values
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function useDynamicSpacing(id: string, config: Omit<SpacingDialConfig, 'type'>): string {
|
||||
const { manifest } = useDialsContext();
|
||||
|
||||
// Build config with optional manifest defaults
|
||||
const finalConfig: SpacingDialConfig = { ...config, type: 'spacing' as const };
|
||||
|
||||
// If options not provided and we have a manifest, load from manifest spacing
|
||||
if (!config.options && manifest?.spacing?.values) {
|
||||
finalConfig.options = manifest.spacing.values;
|
||||
}
|
||||
|
||||
return useDial<string>(id, 'spacing', finalConfig);
|
||||
}
|
||||
29
packages/dials/src/hooks/useDynamicVariant.ts
Normal file
29
packages/dials/src/hooks/useDynamicVariant.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* React hook for creating a variant dial
|
||||
*/
|
||||
|
||||
import { useDial } from './useDial';
|
||||
import type { VariantDialConfig } from '../types';
|
||||
|
||||
/**
|
||||
* Create a dynamic variant dial for discrete choices
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const layout = useDynamicVariant('dashboard-layout', {
|
||||
* label: 'Layout Style',
|
||||
* default: 'grid',
|
||||
* options: ['grid', 'list', 'compact'] as const,
|
||||
* group: 'Dashboard'
|
||||
* });
|
||||
*
|
||||
* {layout === 'grid' && <GridView />}
|
||||
* {layout === 'list' && <ListView />}
|
||||
* ```
|
||||
*/
|
||||
export function useDynamicVariant<T extends string>(
|
||||
id: string,
|
||||
config: Omit<VariantDialConfig<T>, 'type'>,
|
||||
): T {
|
||||
return useDial<T>(id, 'variant', { ...config, type: 'variant' });
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue