Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.cedarcopilot.com/llms.txt

Use this file to discover all available pages before exploring further.

Slider Spell

The SliderSpell component creates an interactive horizontal slider that appears at the cursor position, perfect for making precise numerical adjustments. It features smooth mouse-based control, customizable ranges, and integrates seamlessly with Cedar’s hold-mode activation pattern.

Features

  • Mouse-Driven Control: Horizontal mouse movement directly controls slider value
  • Hold Mode Integration: Activates on key/mouse hold, executes on release
  • Customizable Ranges: Configure min, max, step, and units
  • Visual Feedback: Real-time value display above slider thumb
  • Smart Positioning: Appears above cursor, centered horizontally
  • Theme Integration: Adapts to Cedar’s light/dark mode
  • Smooth Animations: Container3D styling with smooth transitions

Installation

import { SliderSpell } from 'cedar-os-components/spells';

Basic Usage

import { SliderSpell } from 'cedar-os-components/spells';
import { ActivationMode } from 'cedar-os';

function MyComponent() {
	const handleSliderComplete = (value: number, store: CedarStore) => {
		console.log('Selected value:', value);
		// Use the value in your application
		updateOpacity(value / 100);
	};

	return (
		<SliderSpell
			spellId='opacity-slider'
			sliderConfig={{
				min: 0,
				max: 100,
				step: 1,
				unit: '%',
				label: 'Opacity',
			}}
			activationConditions={{
				events: ['o'],
				mode: ActivationMode.HOLD,
			}}
			onComplete={handleSliderComplete}
		/>
	);
}
Usage: Hold ‘O’ key and move mouse horizontally to adjust opacity from 0-100%.

Props

SliderSpell Props

PropTypeRequiredDescription
spellIdstringYesUnique identifier for this spell instance
sliderConfigSliderConfigNoConfiguration for the slider
onComplete(value, store) => voidYesCallback when value is confirmed
activationConditionsActivationConditionsYesConditions that trigger the slider

SliderConfig Interface

interface SliderConfig {
	/** Minimum value for the slider (default: 0) */
	min?: number;

	/** Maximum value for the slider (default: 100) */
	max?: number;

	/** Step increment for the slider (default: 1) */
	step?: number;

	/** Label to display below the slider (optional) */
	label?: string;

	/** Unit to display after the value (default: '%') */
	unit?: string;
}

Hold Mode Pattern

The SliderSpell is designed for hold-mode interaction, providing natural and efficient value selection:
activationConditions={{
  events: ['s'],
  mode: ActivationMode.HOLD
}}
User flow:
  1. Hold key (or right-click) to activate slider
  2. Move mouse horizontally to adjust value
  3. Release to confirm selection and execute callback
  4. Press ESC while holding to cancel

Advanced Examples

Volume Control

Create a volume slider with audio feedback:
const volumeSlider = (
	<SliderSpell
		spellId='volume-control'
		sliderConfig={{
			min: 0,
			max: 100,
			step: 5,
			unit: '%',
			label: 'Volume',
		}}
		activationConditions={{
			events: ['v'],
			mode: ActivationMode.HOLD,
		}}
		onComplete={(value, store) => {
			// Update audio volume
			const audio = document.querySelector('audio');
			if (audio) {
				audio.volume = value / 100;
			}

			// Store in preferences
			localStorage.setItem('volume', value.toString());

			// Optional: Show confirmation
			store.addMessage({
				content: `Volume set to ${value}%`,
				role: 'system',
			});
		}}
	/>
);

Color Opacity Adjuster

Adjust transparency for design tools:
const opacitySlider = (
	<SliderSpell
		spellId='opacity-adjuster'
		sliderConfig={{
			min: 0,
			max: 255,
			step: 1,
			unit: '',
			label: 'Alpha Channel',
		}}
		activationConditions={{
			events: ['a', 'right-click'],
			mode: ActivationMode.HOLD,
		}}
		onComplete={(value, store) => {
			// Update selected element opacity
			const selectedElement = document.querySelector('.selected');
			if (selectedElement) {
				selectedElement.style.opacity = (value / 255).toString();
			}

			// Update design state
			store.setCedarState('selectedOpacity', value);
		}}
	/>
);

Temperature Sensor Reading

For scientific or IoT applications:
const temperatureSlider = (
	<SliderSpell
		spellId='temperature-setter'
		sliderConfig={{
			min: -20,
			max: 40,
			step: 0.5,
			unit: '°C',
			label: 'Target Temperature',
		}}
		activationConditions={{
			events: ['t'],
			mode: ActivationMode.HOLD,
		}}
		onComplete={async (value, store) => {
			try {
				// Send to IoT device
				await fetch('/api/thermostat', {
					method: 'POST',
					body: JSON.stringify({ targetTemp: value }),
				});

				// Confirm with AI assistant
				await store.sendMessage({
					content: `Set thermostat to ${value}°C`,
					role: 'user',
				});
			} catch (error) {
				console.error('Failed to set temperature:', error);
			}
		}}
	/>
);

Multiple Sliders System

Coordinate multiple sliders for complex adjustments:
function ColorAdjustmentSystem() {
	const [currentColor, setCurrentColor] = useState({ r: 128, g: 128, b: 128 });

	const updateColorChannel =
		(channel: 'r' | 'g' | 'b') => (value: number, store: CedarStore) => {
			const newColor = { ...currentColor, [channel]: value };
			setCurrentColor(newColor);

			// Apply to selected element
			const element = document.querySelector('.color-target');
			if (element) {
				element.style.backgroundColor = `rgb(${newColor.r}, ${newColor.g}, ${newColor.b})`;
			}
		};

	return (
		<>
			{/* Red Channel */}
			<SliderSpell
				spellId='red-channel'
				sliderConfig={{ min: 0, max: 255, unit: '', label: 'Red' }}
				activationConditions={{ events: ['r'], mode: ActivationMode.HOLD }}
				onComplete={updateColorChannel('r')}
			/>

			{/* Green Channel */}
			<SliderSpell
				spellId='green-channel'
				sliderConfig={{ min: 0, max: 255, unit: '', label: 'Green' }}
				activationConditions={{ events: ['g'], mode: ActivationMode.HOLD }}
				onComplete={updateColorChannel('g')}
			/>

			{/* Blue Channel */}
			<SliderSpell
				spellId='blue-channel'
				sliderConfig={{ min: 0, max: 255, unit: '', label: 'Blue' }}
				activationConditions={{ events: ['b'], mode: ActivationMode.HOLD }}
				onComplete={updateColorChannel('b')}
			/>
		</>
	);
}

Dynamic Range Slider

Adjust slider range based on context:
function DynamicSlider({
	mode,
}: {
	mode: 'percentage' | 'pixels' | 'degrees';
}) {
	const sliderConfig = useMemo(() => {
		switch (mode) {
			case 'percentage':
				return { min: 0, max: 100, step: 1, unit: '%' };
			case 'pixels':
				return { min: 0, max: 1920, step: 1, unit: 'px' };
			case 'degrees':
				return { min: 0, max: 360, step: 1, unit: '°' };
			default:
				return { min: 0, max: 100, step: 1, unit: '%' };
		}
	}, [mode]);

	return (
		<SliderSpell
			spellId={`dynamic-slider-${mode}`}
			sliderConfig={sliderConfig}
			activationConditions={{
				events: ['d'],
				mode: ActivationMode.HOLD,
			}}
			onComplete={(value, store) => {
				console.log(`${mode} value:`, value);
				// Handle based on current mode
			}}
		/>
	);
}

Mouse Sensitivity

The slider uses a sensitivity system to map mouse movement to value changes:
// Default sensitivity: 300 pixels for full range
const sensitivity = 300;
const valueRange = max - min;
const deltaValue = (mouseMovement / sensitivity) * valueRange;

Customizing Sensitivity

While not directly configurable, you can adjust effective sensitivity by modifying your range:
// More sensitive (smaller movements = bigger changes)
sliderConfig={{ min: 0, max: 10, step: 0.1 }}

// Less sensitive (larger movements needed)
sliderConfig={{ min: 0, max: 1000, step: 10 }}

Visual Behavior

Positioning

The slider appears above the cursor, centered horizontally:
  • Mouse Events: Positioned at cursor location
  • Keyboard Events: Positioned at viewport center
  • Above Cursor: Positioned with 8px gap above cursor
  • Centered: Horizontally centered on activation point

Value Display

  • Real-time Updates: Value shown above slider thumb
  • Unit Display: Configured unit appears after value
  • Theme Adaptive: Colors adapt to light/dark mode
  • Smooth Transitions: No jarring animations, just smooth updates

Styling Integration

The component uses Cedar’s styling system:
  • Container3D: 3D-styled slider track and thumb
  • Theme Colors: Respects dark/light mode preferences
  • Consistent Typography: Matches Cedar’s text styling
  • Backdrop Blur: Subtle backdrop blur for better visibility

Interaction Details

Mouse Control

  • Horizontal Movement: Only horizontal mouse movement affects value
  • Relative Positioning: Movement relative to activation point
  • Continuous Updates: Value updates in real-time during movement
  • Clamping: Values automatically clamped to min/max range
  • Step Snapping: Values snap to configured step increments

Keyboard Support

  • ESC: Cancel slider without executing callback
  • Hold Pattern: Must hold activation key throughout interaction
  • Release to Confirm: Release key to execute callback with final value

Edge Cases

  • Viewport Boundaries: Slider stays within viewport bounds
  • Rapid Movement: Handles fast mouse movements smoothly
  • Step Precision: Ensures values align with step configuration

Best Practices

1. Choose Appropriate Ranges

// ✅ Good: Reasonable range for the use case
sliderConfig={{ min: 0, max: 100, step: 5 }} // Volume control

// ❌ Avoid: Excessive precision
sliderConfig={{ min: 0, max: 100, step: 0.001 }} // Too precise for UI

2. Use Meaningful Units

// ✅ Good: Clear units
sliderConfig={{ unit: '%' }}   // Percentage
sliderConfig={{ unit: 'px' }}  // Pixels
sliderConfig={{ unit: '°' }}   // Degrees

// ❌ Avoid: Ambiguous or missing units
sliderConfig={{ unit: '' }}    // What does this number mean?

3. Logical Key Bindings

// ✅ Good: Mnemonic key bindings
events: ['v']; // Volume
events: ['o']; // Opacity
events: ['s']; // Size/Scale

// ❌ Avoid: Random key assignments
events: ['x']; // For volume control?

4. Handle Errors Gracefully

onComplete: async (value, store) => {
	try {
		await updateSetting(value);
	} catch (error) {
		console.error('Update failed:', error);
		// Provide user feedback
		store.addMessage({
			content: 'Failed to update setting. Please try again.',
			role: 'system',
		});
	}
};

5. Provide Visual Context

// Include labels for clarity
sliderConfig={{
  min: 0,
  max: 100,
  unit: '%',
  label: 'Volume' // Helps users understand what they're adjusting
}}

Performance Considerations

Efficient Updates

  • Throttled Rendering: Value updates are smooth but not excessive
  • Ref-based State: Uses refs to avoid unnecessary re-renders
  • Conditional Rendering: Only renders when active
  • Memory Cleanup: Automatically cleans up event listeners

Event Handling

  • Single Listener: Uses efficient event delegation
  • Debounced Actions: Prevents spam during rapid movements
  • Optimized Calculations: Minimal computation per mouse move

Accessibility

Keyboard Navigation

  • The slider is primarily mouse-driven but keyboard accessible
  • ESC key provides clear exit path
  • Hold pattern is intuitive for keyboard users

Screen Reader Support

Consider providing alternative interfaces for screen readers:
// Provide aria-label context
<div aria-label={`Adjust ${label} from ${min} to ${max} ${unit}`}>
	<SliderSpell {...props} />
</div>

Alternative Access

Provide multiple ways to adjust values:
function AccessibleSlider() {
	return (
		<>
			<SliderSpell {...sliderProps} />

			{/* Alternative input for precise entry */}
			<input
				type='number'
				min={min}
				max={max}
				step={step}
				aria-label='Precise value entry'
			/>
		</>
	);
}

Common Patterns

Media Controls

// Volume, playback speed, seek position
const mediaSliders = [
	{ key: 'v', label: 'Volume', min: 0, max: 100, unit: '%' },
	{ key: 's', label: 'Speed', min: 0.25, max: 2, unit: 'x', step: 0.25 },
	{ key: 't', label: 'Position', min: 0, max: duration, unit: 's' },
];

Design Tools

// Size, opacity, rotation, spacing
const designSliders = [
	{ key: 'w', label: 'Width', min: 10, max: 500, unit: 'px' },
	{ key: 'h', label: 'Height', min: 10, max: 500, unit: 'px' },
	{ key: 'a', label: 'Alpha', min: 0, max: 255, unit: '' },
	{ key: 'r', label: 'Rotation', min: 0, max: 360, unit: '°' },
];

Scientific Instruments

// Temperature, pressure, flow rate
const instrumentSliders = [
	{ key: 't', label: 'Temperature', min: -273, max: 1000, unit: '°C' },
	{ key: 'p', label: 'Pressure', min: 0, max: 10, unit: 'bar' },
	{ key: 'f', label: 'Flow Rate', min: 0, max: 100, unit: 'L/min' },
];

Troubleshooting

Slider not appearing

  • Check that spell ID is unique
  • Verify activation conditions are correct
  • Ensure component is mounted
  • Check for conflicting event handlers

Values not updating

  • Verify mouse movement is being detected
  • Check min/max/step configuration
  • Ensure onComplete callback is defined
  • Check for JavaScript errors

Position issues

  • Verify cursor position detection
  • Check for CSS transforms on parent elements
  • Ensure proper z-index for visibility
  • Check viewport boundary calculations

Performance problems

  • Reduce update frequency if needed
  • Check for memory leaks in callbacks
  • Verify event listeners are cleaned up
  • Monitor re-render frequency