fix: Sticky submenu + emoji flags instead of images
1. Made Settings Submenu Sticky ✅ Problem: Settings submenu wasn't sticky like Dashboard Solution: Added sticky positioning to SubmenuBar Added classes: - sticky top-0 z-20 - bg-background/95 backdrop-blur - supports-[backdrop-filter]:bg-background/60 Result: ✅ Settings submenu now stays at top when scrolling 2. Switched to Emoji Flags ✅ Problem: Base64 images not showing in select options Better Solution: Use native emoji flags Benefits: - ✅ No image loading required - ✅ Native OS rendering - ✅ Smaller bundle size - ✅ Better performance - ✅ Always works (no broken images) Implementation: function countryCodeToEmoji(countryCode: string): string { const codePoints = countryCode .toUpperCase() .split('') .map(char => 127397 + char.charCodeAt(0)); return String.fromCodePoint(...codePoints); } // AE → 🇦🇪 // US → 🇺🇸 // ID → 🇮🇩 3. Updated Currency Select ✅ Before: [Image] United Arab Emirates dirham (AED) After: 🇦🇪 United Arab Emirates dirham (AED) - Emoji flag in label - No separate icon prop needed - Works immediately 4. Updated Store Summary ✅ Before: [Image] Your store is located in Indonesia After: 🇮🇩 Your store is located in Indonesia - Dynamic emoji flag based on currency - Cleaner implementation - No image loading 5. Simplified SearchableSelect ✅ - Removed icon prop (not needed with emoji) - Removed image rendering code - Simpler component API Files Modified: - SubmenuBar.tsx: Added sticky positioning - Store.tsx: Emoji flags + helper function - searchable-select.tsx: Removed icon support Why Emoji > Images: ✅ Universal support (all modern browsers/OS) ✅ No loading time ✅ No broken images ✅ Smaller code ✅ Native rendering ✅ Accessibility friendly
This commit is contained in:
@@ -12,7 +12,7 @@ export default function SubmenuBar({ items = [] }: Props) {
|
|||||||
if (items.length === 0) return null;
|
if (items.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-submenubar className="border-b border-border bg-background/95">
|
<div data-submenubar className="border-b border-border bg-background md:bg-background/95 md:backdrop-blur md:supports-[backdrop-filter]:bg-background/60 sticky top-0 z-20">
|
||||||
<div className="px-4 py-2">
|
<div className="px-4 py-2">
|
||||||
<div className="flex gap-2 overflow-x-auto no-scrollbar">
|
<div className="flex gap-2 overflow-x-auto no-scrollbar">
|
||||||
{items.map((it) => {
|
{items.map((it) => {
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ export interface Option {
|
|||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
/** Optional text used for filtering. Falls back to string label or value. */
|
/** Optional text used for filtering. Falls back to string label or value. */
|
||||||
searchText?: string;
|
searchText?: string;
|
||||||
/** Optional icon (base64 image or URL) to display before the label */
|
|
||||||
icon?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -67,12 +65,7 @@ export function SearchableSelect({
|
|||||||
aria-disabled={disabled}
|
aria-disabled={disabled}
|
||||||
tabIndex={disabled ? -1 : 0}
|
tabIndex={disabled ? -1 : 0}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
{selected ? selected.label : placeholder}
|
||||||
{selected?.icon && (
|
|
||||||
<img src={selected.icon} alt="" className="w-5 h-4 object-cover rounded-sm flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
<span className="truncate">{selected ? selected.label : placeholder}</span>
|
|
||||||
</div>
|
|
||||||
<ChevronsUpDown className="opacity-50 h-4 w-4 shrink-0" />
|
<ChevronsUpDown className="opacity-50 h-4 w-4 shrink-0" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
@@ -111,10 +104,7 @@ export function SearchableSelect({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{opt.icon && (
|
{opt.label}
|
||||||
<img src={opt.icon} alt="" className="w-5 h-4 object-cover rounded-sm mr-2 flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
<span className="truncate">{opt.label}</span>
|
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
))}
|
))}
|
||||||
</CommandList>
|
</CommandList>
|
||||||
|
|||||||
@@ -10,6 +10,16 @@ import { SearchableSelect } from '@/components/ui/searchable-select';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import flagsData from '@/data/flags.json';
|
import flagsData from '@/data/flags.json';
|
||||||
|
|
||||||
|
// Convert country code to emoji flag
|
||||||
|
function countryCodeToEmoji(countryCode: string): string {
|
||||||
|
if (!countryCode || countryCode.length !== 2) return '';
|
||||||
|
const codePoints = countryCode
|
||||||
|
.toUpperCase()
|
||||||
|
.split('')
|
||||||
|
.map(char => 127397 + char.charCodeAt(0));
|
||||||
|
return String.fromCodePoint(...codePoints);
|
||||||
|
}
|
||||||
|
|
||||||
interface StoreSettings {
|
interface StoreSettings {
|
||||||
storeName: string;
|
storeName: string;
|
||||||
contactEmail: string;
|
contactEmail: string;
|
||||||
@@ -302,16 +312,14 @@ export default function StoreDetailsPage() {
|
|||||||
? currency.code
|
? currency.code
|
||||||
: currency.symbol;
|
: currency.symbol;
|
||||||
|
|
||||||
// Find matching flag data
|
// Find matching flag data and convert to emoji
|
||||||
const flagInfo = flagsData.find((f: any) => f.code === currency.code);
|
const flagInfo = flagsData.find((f: any) => f.code === currency.code);
|
||||||
|
const flagEmoji = flagInfo ? countryCodeToEmoji(flagInfo.countryCode) : '';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value: currency.code,
|
value: currency.code,
|
||||||
label: flagInfo
|
label: `${flagEmoji} ${currency.name} (${displaySymbol})`.trim(),
|
||||||
? `${currency.name} (${displaySymbol})`
|
|
||||||
: `${currency.name} (${displaySymbol})`,
|
|
||||||
searchText: `${currency.code} ${currency.name} ${displaySymbol}`,
|
searchText: `${currency.code} ${currency.name} ${displaySymbol}`,
|
||||||
icon: flagInfo?.flag, // Add flag as icon
|
|
||||||
};
|
};
|
||||||
})}
|
})}
|
||||||
placeholder="Select currency..."
|
placeholder="Select currency..."
|
||||||
@@ -439,21 +447,13 @@ export default function StoreDetailsPage() {
|
|||||||
// Find flag for current currency
|
// Find flag for current currency
|
||||||
const currencyFlag = flagsData.find((f: any) => f.code === settings.currency);
|
const currencyFlag = flagsData.find((f: any) => f.code === settings.currency);
|
||||||
const currencyInfo = currencies.find((c: any) => c.code === settings.currency);
|
const currencyInfo = currencies.find((c: any) => c.code === settings.currency);
|
||||||
|
const flagEmoji = currencyFlag ? countryCodeToEmoji(currencyFlag.countryCode) : '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{currencyFlag?.flag && (
|
|
||||||
<img
|
|
||||||
src={currencyFlag.flag}
|
|
||||||
alt={currencyFlag.country}
|
|
||||||
className="w-6 h-4 object-cover rounded-sm"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<p className="text-sm font-medium">
|
<p className="text-sm font-medium">
|
||||||
Your store is located in {currencyFlag?.country || settings.country}
|
{flagEmoji} Your store is located in {currencyFlag?.country || settings.country}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
Prices will be displayed in {currencyInfo?.name || settings.currency} • Timezone: {settings.timezone}
|
Prices will be displayed in {currencyInfo?.name || settings.currency} • Timezone: {settings.timezone}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user