mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-19 18:21:24 +02:00
- Add uload project with apps/web structure
- Reorganize from flat to monorepo structure
- Remove PocketBase binary and local data
- Update to pnpm and @uload/web namespace
- Add picture project to monorepo
- Remove embedded git repository
- Unify all package names to @{project}/{app} schema:
- @maerchenzauber/* (was @storyteller/*)
- @manacore/* (was manacore-*, manacore)
- @manadeck/* (was web, backend, manadeck)
- @memoro/* (was memoro-web, landing, memoro)
- @picture/* (already unified)
- @uload/web
- Add convenient dev scripts for all apps:
- pnpm dev:{project}:web
- pnpm dev:{project}:landing
- pnpm dev:{project}:mobile
- pnpm dev:{project}:backend
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| index.ts | ||
| README.md | ||
| Select.tsx | ||
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