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:
dwindown
2025-11-06 12:08:04 +07:00
parent 2a679ffd15
commit 39a215c188
3 changed files with 20 additions and 30 deletions

View File

@@ -12,7 +12,7 @@ export default function SubmenuBar({ items = [] }: Props) {
if (items.length === 0) return null;
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="flex gap-2 overflow-x-auto no-scrollbar">
{items.map((it) => {

View File

@@ -22,8 +22,6 @@ export interface Option {
label: React.ReactNode;
/** Optional text used for filtering. Falls back to string label or value. */
searchText?: string;
/** Optional icon (base64 image or URL) to display before the label */
icon?: string;
}
interface Props {
@@ -67,12 +65,7 @@ export function SearchableSelect({
aria-disabled={disabled}
tabIndex={disabled ? -1 : 0}
>
<div className="flex items-center gap-2 flex-1 min-w-0">
{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>
{selected ? selected.label : placeholder}
<ChevronsUpDown className="opacity-50 h-4 w-4 shrink-0" />
</Button>
</PopoverTrigger>
@@ -111,10 +104,7 @@ export function SearchableSelect({
)}
/>
)}
{opt.icon && (
<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>
{opt.label}
</CommandItem>
))}
</CommandList>

View File

@@ -10,6 +10,16 @@ import { SearchableSelect } from '@/components/ui/searchable-select';
import { toast } from 'sonner';
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 {
storeName: string;
contactEmail: string;
@@ -302,16 +312,14 @@ export default function StoreDetailsPage() {
? currency.code
: 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 flagEmoji = flagInfo ? countryCodeToEmoji(flagInfo.countryCode) : '';
return {
value: currency.code,
label: flagInfo
? `${currency.name} (${displaySymbol})`
: `${currency.name} (${displaySymbol})`,
label: `${flagEmoji} ${currency.name} (${displaySymbol})`.trim(),
searchText: `${currency.code} ${currency.name} ${displaySymbol}`,
icon: flagInfo?.flag, // Add flag as icon
};
})}
placeholder="Select currency..."
@@ -439,21 +447,13 @@ export default function StoreDetailsPage() {
// Find flag for current currency
const currencyFlag = flagsData.find((f: any) => f.code === settings.currency);
const currencyInfo = currencies.find((c: any) => c.code === settings.currency);
const flagEmoji = currencyFlag ? countryCodeToEmoji(currencyFlag.countryCode) : '';
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">
Your store is located in {currencyFlag?.country || settings.country}
{flagEmoji} Your store is located in {currencyFlag?.country || settings.country}
</p>
</div>
<p className="text-sm text-muted-foreground mt-1">
Prices will be displayed in {currencyInfo?.name || settings.currency} Timezone: {settings.timezone}
</p>