Merge pull request #17 from ualsweb/claude/bakery-settings-ux-redesign-017J8nkGyr5NagisnW1TRPSs

Claude/bakery settings ux redesign 017 j8nk gyr5 nagisn w1 trp ss
This commit is contained in:
ualsweb
2025-11-14 07:49:47 +01:00
committed by GitHub
16 changed files with 1599 additions and 1261 deletions

View File

@@ -0,0 +1,180 @@
import React from 'react';
import { HelpCircle } from 'lucide-react';
import { Tooltip } from '../Tooltip';
import { Toggle } from '../Toggle';
import { Input } from '../Input';
import { Select } from '../Select';
import { Badge } from '../Badge';
export interface SettingRowProps {
label: string;
description?: string;
helpText?: string;
icon?: React.ReactNode;
badge?: {
text: string;
variant?: 'default' | 'success' | 'warning' | 'danger' | 'info';
};
// For toggle type
type?: 'toggle' | 'input' | 'select' | 'custom';
checked?: boolean;
onToggle?: (checked: boolean) => void;
// For input type
inputType?: 'text' | 'number' | 'email' | 'tel' | 'password';
value?: string | number;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
placeholder?: string;
min?: number;
max?: number;
step?: number;
error?: string;
// For select type
options?: Array<{ value: string; label: string }>;
selectValue?: string;
onSelectChange?: (value: string) => void;
// For custom content
children?: React.ReactNode;
// Common props
disabled?: boolean;
className?: string;
required?: boolean;
}
const SettingRow: React.FC<SettingRowProps> = ({
label,
description,
helpText,
icon,
badge,
type = 'toggle',
checked,
onToggle,
inputType = 'text',
value,
onChange,
placeholder,
min,
max,
step,
error,
options = [],
selectValue,
onSelectChange,
children,
disabled = false,
className = '',
required = false,
}) => {
const renderControl = () => {
switch (type) {
case 'toggle':
return (
<Toggle
checked={checked || false}
onChange={onToggle || (() => {})}
disabled={disabled}
size="md"
/>
);
case 'input':
return (
<div className="w-full max-w-xs">
<Input
type={inputType}
value={value}
onChange={onChange}
placeholder={placeholder}
min={min}
max={max}
step={step}
error={error}
disabled={disabled}
required={required}
/>
</div>
);
case 'select':
return (
<div className="w-full max-w-xs">
<Select
options={options}
value={selectValue}
onChange={onSelectChange}
disabled={disabled}
required={required}
/>
</div>
);
case 'custom':
return children;
default:
return null;
}
};
return (
<div className={`
flex flex-col sm:flex-row sm:items-center sm:justify-between
gap-3 sm:gap-6 py-4 px-4 sm:px-6
border-b border-[var(--border-primary)] last:border-b-0
hover:bg-[var(--bg-secondary)] transition-colors
${className}
`}>
{/* Left side - Label and Description */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
{icon && (
<div className="flex-shrink-0 text-[var(--text-secondary)]">
{icon}
</div>
)}
<label className="text-sm font-medium text-[var(--text-primary)] flex items-center gap-2">
{label}
{required && <span className="text-red-500">*</span>}
</label>
{badge && (
<Badge variant={badge.variant || 'default'} size="sm">
{badge.text}
</Badge>
)}
{helpText && (
<Tooltip content={helpText}>
<HelpCircle className="w-4 h-4 text-[var(--text-tertiary)] cursor-help" />
</Tooltip>
)}
</div>
{description && (
<p className="text-xs text-[var(--text-secondary)] mt-1">
{description}
</p>
)}
{error && (
<p className="text-xs text-red-500 mt-1">
{error}
</p>
)}
</div>
{/* Right side - Control */}
<div className="flex-shrink-0">
{renderControl()}
</div>
</div>
);
};
export default SettingRow;

View File

@@ -0,0 +1,2 @@
export { default as SettingRow } from './SettingRow';
export type { SettingRowProps } from './SettingRow';

View File

@@ -0,0 +1,108 @@
import React, { useState } from 'react';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { Card } from '../Card';
import { Badge } from '../Badge';
export interface SettingSectionProps {
title: string;
description?: string;
icon?: React.ReactNode;
badge?: {
text: string;
variant?: 'default' | 'success' | 'warning' | 'danger' | 'info';
};
collapsible?: boolean;
defaultOpen?: boolean;
children: React.ReactNode;
className?: string;
headerAction?: React.ReactNode;
}
const SettingSection: React.FC<SettingSectionProps> = ({
title,
description,
icon,
badge,
collapsible = false,
defaultOpen = true,
children,
className = '',
headerAction,
}) => {
const [isOpen, setIsOpen] = useState(defaultOpen);
const handleToggle = () => {
if (collapsible) {
setIsOpen(!isOpen);
}
};
return (
<Card className={`overflow-hidden ${className}`}>
{/* Header */}
<div
className={`
px-4 sm:px-6 py-4
${collapsible ? 'cursor-pointer hover:bg-[var(--bg-secondary)]' : ''}
transition-colors
`}
onClick={handleToggle}
>
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3 flex-1 min-w-0">
{icon && (
<div className="flex-shrink-0 mt-0.5 text-[var(--color-primary)]">
{icon}
</div>
)}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h3 className="text-base sm:text-lg font-semibold text-[var(--text-primary)]">
{title}
</h3>
{badge && (
<Badge variant={badge.variant || 'default'} size="sm">
{badge.text}
</Badge>
)}
{collapsible && (
<div className="ml-auto flex-shrink-0">
{isOpen ? (
<ChevronUp className="w-5 h-5 text-[var(--text-secondary)]" />
) : (
<ChevronDown className="w-5 h-5 text-[var(--text-secondary)]" />
)}
</div>
)}
</div>
{description && (
<p className="text-sm text-[var(--text-secondary)] mt-1">
{description}
</p>
)}
</div>
</div>
{headerAction && !collapsible && (
<div className="flex-shrink-0">
{headerAction}
</div>
)}
</div>
</div>
{/* Content */}
{(!collapsible || isOpen) && (
<div className="border-t border-[var(--border-primary)]">
{children}
</div>
)}
</Card>
);
};
export default SettingSection;

View File

@@ -0,0 +1,2 @@
export { default as SettingSection } from './SettingSection';
export type { SettingSectionProps } from './SettingSection';

View File

@@ -0,0 +1,63 @@
import React, { useState, useEffect } from 'react';
import { Search, X } from 'lucide-react';
export interface SettingsSearchProps {
placeholder?: string;
onSearch: (query: string) => void;
className?: string;
}
const SettingsSearch: React.FC<SettingsSearchProps> = ({
placeholder = 'Search settings...',
onSearch,
className = '',
}) => {
const [query, setQuery] = useState('');
useEffect(() => {
const debounceTimer = setTimeout(() => {
onSearch(query);
}, 300);
return () => clearTimeout(debounceTimer);
}, [query, onSearch]);
const handleClear = () => {
setQuery('');
onSearch('');
};
return (
<div className={`relative ${className}`}>
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-[var(--text-tertiary)]" />
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder={placeholder}
className="w-full pl-10 pr-10 py-2.5 border border-[var(--border-primary)] rounded-lg bg-[var(--bg-primary)] text-[var(--text-primary)] placeholder-[var(--text-tertiary)] focus:ring-2 focus:ring-[var(--color-primary)] focus:border-transparent transition-all"
/>
{query && (
<button
onClick={handleClear}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)] transition-colors"
aria-label="Clear search"
>
<X className="w-5 h-5" />
</button>
)}
</div>
{query && (
<div className="absolute top-full left-0 right-0 mt-2 p-2 bg-[var(--bg-secondary)] border border-[var(--border-primary)] rounded-lg shadow-lg z-10">
<p className="text-xs text-[var(--text-secondary)]">
Searching for: <span className="font-semibold text-[var(--text-primary)]">"{query}"</span>
</p>
</div>
)}
</div>
);
};
export default SettingsSearch;

View File

@@ -0,0 +1,2 @@
export { default as SettingsSearch } from './SettingsSearch';
export type { SettingsSearchProps } from './SettingsSearch';

View File

@@ -40,6 +40,9 @@ export { TableOfContents } from './TableOfContents';
export { SavingsCalculator } from './SavingsCalculator';
export { StepTimeline } from './StepTimeline';
export { FAQAccordion } from './FAQAccordion';
export { SettingRow } from './SettingRow';
export { SettingSection } from './SettingSection';
export { SettingsSearch } from './SettingsSearch';
// Export types
export type { ButtonProps } from './Button';
@@ -77,4 +80,7 @@ export type { FloatingCTAProps } from './FloatingCTA';
export type { TableOfContentsProps, TOCSection } from './TableOfContents';
export type { SavingsCalculatorProps } from './SavingsCalculator';
export type { StepTimelineProps, TimelineStep } from './StepTimeline';
export type { FAQAccordionProps, FAQItem } from './FAQAccordion';
export type { FAQAccordionProps, FAQItem } from './FAQAccordion';
export type { SettingRowProps } from './SettingRow';
export type { SettingSectionProps } from './SettingSection';
export type { SettingsSearchProps } from './SettingsSearch';