managarten/apps/picture/packages/mobile-ui/components/ui/Select
Wuesteon d36b321d9d style: auto-format codebase with Prettier
Applied formatting to 1487+ files using pnpm format:write
  - TypeScript/JavaScript files
  - Svelte components
  - Astro pages
  - JSON configs
  - Markdown docs

  13 files still need manual review (Astro JSX comments)
2025-11-27 18:33:16 +01:00
..
index.ts refactor: restructure 2025-11-26 03:03:24 +01:00
README.md style: auto-format codebase with Prettier 2025-11-27 18:33:16 +01:00
Select.tsx style: auto-format codebase with Prettier 2025-11-27 18:33:16 +01:00

Select

Horizontal scrollable selector component for choosing between multiple options.

Features

  • Horizontal scrolling options
  • Icons and emoji support
  • Optional subtitles and descriptions
  • Loading and error states
  • Info toggle for details
  • Fully customizable colors
  • Disabled state

Installation

npx @memoro/ui add select

Dependencies: text, icon

Usage

Basic Select

import { Select, SelectOption } from '@/components/ui/Select';

const options: SelectOption[] = [
	{ id: '1', label: 'Option 1', icon: '🎨' },
	{ id: '2', label: 'Option 2', icon: '🖼️' },
	{ id: '3', label: 'Option 3', icon: '📸' },
];

<Select options={options} selectedId="1" onSelect={(option) => console.log('Selected:', option)} />;

With Title

<Select
	title="Choose a theme"
	options={themeOptions}
	selectedId={selectedTheme}
	onSelect={setSelectedTheme}
/>

With Subtitles and Descriptions

const options: SelectOption[] = [
	{
		id: 'basic',
		label: 'Basic',
		icon: '⚡',
		subtitle: 'Fast & Simple',
		description: 'Quick generation with basic settings',
	},
	{
		id: 'advanced',
		label: 'Advanced',
		icon: '🚀',
		subtitle: 'Powerful',
		description: 'Full control over all parameters',
	},
];

<Select
	options={options}
	selectedId={selectedMode}
	onSelect={(option) => setSelectedMode(option.id)}
/>;

Loading State

<Select options={[]} selectedId={null} onSelect={() => {}} loading={true} />

Error State

<Select
	options={[]}
	selectedId={null}
	onSelect={() => {}}
	error="Failed to load options"
	onRetry={() => refetch()}
/>

Custom Colors

<Select
	options={options}
	selectedId={selectedId}
	onSelect={handleSelect}
	backgroundColor="#F9FAFB"
	borderColor="#E5E7EB"
	selectedBackgroundColor="#10B981"
	selectedBorderColor="#10B981"
	textColor="#111827"
	selectedTextColor="#FFFFFF"
/>

Custom Width

<Select options={options} selectedId={selectedId} onSelect={handleSelect} minWidth={200} />

Props

Prop Type Default Description
options SelectOption[] - Required - Options to display
selectedId string | null - Required - Currently selected option ID
onSelect (option: SelectOption) => void - Required - Callback when option is selected
loading boolean false Show loading state
error string | null null Error message
onRetry () => void - Retry callback for error state
disabled boolean false Disable selection
minWidth number 160 Minimum width for each option
title string - Title above options
backgroundColor string '#FFFFFF' Background color for unselected options
borderColor string '#E5E7EB' Border color for unselected options
selectedBackgroundColor string '#3B82F6' Background color for selected option
selectedBorderColor string '#3B82F6' Border color for selected option
textColor string '#111827' Text color for unselected options
selectedTextColor string '#FFFFFF' Text color for selected option
style ViewStyle - Additional styles

SelectOption Type

type SelectOption = {
	id: string;
	label: string;
	subtitle?: string;
	icon?: string;
	description?: string;
};

Default Styling

  • Background: White (#FFFFFF)
  • Border: Light gray (#E5E7EB)
  • Selected Background: Blue (#3B82F6)
  • Selected Border: Blue (#3B82F6)
  • Text Color: Dark gray (#111827)
  • Selected Text: White (#FFFFFF)
  • Min Width: 160px
  • Border Radius: 12px
  • Padding: 16px

Examples

Size Selector

const sizeOptions: SelectOption[] = [
	{ id: 'sm', label: 'Small', icon: '📦', subtitle: '256x256' },
	{ id: 'md', label: 'Medium', icon: '📦', subtitle: '512x512' },
	{ id: 'lg', label: 'Large', icon: '📦', subtitle: '1024x1024' },
];

<Select
	title="Image Size"
	options={sizeOptions}
	selectedId={selectedSize}
	onSelect={(opt) => setSelectedSize(opt.id)}
/>;

Style Selector

const styleOptions: SelectOption[] = [
	{ id: 'realistic', label: 'Realistic', icon: '📷' },
	{ id: 'artistic', label: 'Artistic', icon: '🎨' },
	{ id: 'cartoon', label: 'Cartoon', icon: '🎭' },
	{ id: 'anime', label: 'Anime', icon: '🌸' },
];

<Select
	options={styleOptions}
	selectedId={style}
	onSelect={(opt) => setStyle(opt.id)}
	minWidth={120}
/>;

Model Selector with Details

const modelOptions: SelectOption[] = [
	{
		id: 'gpt4',
		label: 'GPT-4',
		icon: '🧠',
		subtitle: 'Most capable',
		description: 'Best for complex tasks requiring deep understanding',
	},
	{
		id: 'gpt35',
		label: 'GPT-3.5',
		icon: '⚡',
		subtitle: 'Fast',
		description: 'Great for quick responses and simple tasks',
	},
];

<Select
	title="AI Model"
	options={modelOptions}
	selectedId={selectedModel}
	onSelect={(opt) => setSelectedModel(opt.id)}
/>;

Async Loading

function ModelSelector() {
	const [models, setModels] = useState<SelectOption[]>([]);
	const [loading, setLoading] = useState(true);
	const [error, setError] = useState<string | null>(null);

	const loadModels = async () => {
		setLoading(true);
		setError(null);
		try {
			const data = await fetchModels();
			setModels(data);
		} catch (err) {
			setError('Failed to load models');
		} finally {
			setLoading(false);
		}
	};

	useEffect(() => {
		loadModels();
	}, []);

	return (
		<Select
			options={models}
			selectedId={selectedModel}
			onSelect={(opt) => setSelectedModel(opt.id)}
			loading={loading}
			error={error}
			onRetry={loadModels}
		/>
	);
}

Color Scheme Selector

const colorOptions: SelectOption[] = [
	{ id: 'light', label: 'Light', icon: '☀️' },
	{ id: 'dark', label: 'Dark', icon: '🌙' },
	{ id: 'auto', label: 'Auto', icon: '✨' },
];

<Select
	title="Theme"
	options={colorOptions}
	selectedId={theme}
	onSelect={(opt) => setTheme(opt.id)}
	minWidth={100}
/>;

Info Toggle

When options have subtitle or description:

  • Info icon appears in header
  • Clicking toggles visibility of:
    • Subtitles under each option
    • Description panel for selected option

States

Loading

  • Shows spinner with "Loading..." text
  • Disabled interaction

Error

  • Shows error message
  • Optional retry button via onRetry

Disabled

  • Reduces opacity to 0.5
  • Prevents selection

Common Patterns

Multi-Section Select

<View style={{ gap: 24 }}>
	<Select
		title="Model"
		options={modelOptions}
		selectedId={selectedModel}
		onSelect={(opt) => setSelectedModel(opt.id)}
	/>

	<Select
		title="Quality"
		options={qualityOptions}
		selectedId={selectedQuality}
		onSelect={(opt) => setSelectedQuality(opt.id)}
	/>
</View>

Conditional Options

const options = isPremium ? [...basicOptions, ...premiumOptions] : basicOptions;

<Select options={options} selectedId={selectedId} onSelect={handleSelect} />;

Notes

  • Options scroll horizontally
  • Selected option is highlighted with custom colors
  • Icons can be emoji or icon names (when using Icon component)
  • Info toggle only appears when options have details
  • Press feedback with opacity change
  • Fully accessible with proper labeling
  • Works great for 3-8 options
  • For many options, consider vertical list instead