fix(branding): working tooltips, high-contrast selected states, gallery chrome follows accent
This commit is contained in:
@@ -80,7 +80,11 @@ const ColorPickerRow: React.FC<{
|
||||
<div>
|
||||
<label className="flex items-center gap-1.5 text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">
|
||||
{label}
|
||||
<span className="cursor-help text-neutral-400 dark:text-neutral-500" title={help}>
|
||||
<span
|
||||
className="info-tooltip text-neutral-400 dark:text-neutral-500"
|
||||
data-tooltip={help}
|
||||
tabIndex={0}
|
||||
>
|
||||
<Info className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
</label>
|
||||
@@ -301,7 +305,7 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
onClick={() => handlePresetSelect(key)}
|
||||
className={`relative p-4 rounded-lg border-2 transition-all text-left ${
|
||||
selectedPreset === key
|
||||
? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
|
||||
? 'tile-selected'
|
||||
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
|
||||
}`}
|
||||
>
|
||||
@@ -390,7 +394,7 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
onClick={() => handleChange('galleryLayout', layout)}
|
||||
className={`relative p-4 rounded-lg border-2 transition-all ${
|
||||
localTheme.galleryLayout === layout
|
||||
? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
|
||||
? 'tile-selected'
|
||||
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
|
||||
}`}
|
||||
>
|
||||
@@ -737,7 +741,7 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
onClick={() => handleChange('headerStyle', style)}
|
||||
className={`relative p-4 rounded-lg border-2 transition-all ${
|
||||
(localTheme.headerStyle || 'standard') === style
|
||||
? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
|
||||
? 'tile-selected'
|
||||
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
|
||||
}`}
|
||||
>
|
||||
@@ -776,7 +780,7 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
onClick={() => handleChange('heroDividerStyle', divider)}
|
||||
className={`relative p-3 rounded-lg border-2 transition-all ${
|
||||
(localTheme.heroDividerStyle || 'wave') === divider
|
||||
? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
|
||||
? 'tile-selected'
|
||||
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
|
||||
}`}
|
||||
>
|
||||
@@ -816,7 +820,7 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
onClick={() => handleChange('controlsStyle', 'classic')}
|
||||
className={`relative p-4 rounded-lg border-2 transition-all ${
|
||||
(localTheme.controlsStyle || 'classic') === 'classic'
|
||||
? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
|
||||
? 'tile-selected'
|
||||
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
|
||||
}`}
|
||||
>
|
||||
@@ -840,7 +844,7 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
onClick={() => handleChange('controlsStyle', 'sidebar')}
|
||||
className={`relative p-4 rounded-lg border-2 transition-all ${
|
||||
localTheme.controlsStyle === 'sidebar'
|
||||
? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
|
||||
? 'tile-selected'
|
||||
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
|
||||
}`}
|
||||
>
|
||||
@@ -922,7 +926,7 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
}}
|
||||
className={`px-4 py-2 text-sm font-medium rounded-lg border transition-colors ${
|
||||
(localTheme.colorMode || 'light') === mode
|
||||
? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30 text-accent-dark'
|
||||
? 'border-accent-dark bg-accent-dark text-white'
|
||||
: 'border-neutral-300 dark:border-neutral-600 text-neutral-600 dark:text-neutral-400 hover:bg-neutral-50 dark:hover:bg-neutral-800'
|
||||
}`}
|
||||
>
|
||||
@@ -969,7 +973,7 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
onClick={() => onForceColorModeChange(value)}
|
||||
className={`px-4 py-2 text-sm font-medium rounded-lg border transition-colors ${
|
||||
active
|
||||
? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30 text-accent-dark'
|
||||
? 'border-accent-dark bg-accent-dark text-white'
|
||||
: 'border-neutral-300 dark:border-neutral-600 text-neutral-600 dark:text-neutral-400 hover:bg-neutral-50 dark:hover:bg-neutral-800'
|
||||
}`}
|
||||
>
|
||||
@@ -1004,11 +1008,12 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
<h4 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300 uppercase tracking-wide mb-3 flex items-center gap-1.5">
|
||||
{t('branding.colorGroupSurfaces', 'Surfaces')}
|
||||
<span
|
||||
className="cursor-help text-neutral-400 dark:text-neutral-500"
|
||||
title={t(
|
||||
className="info-tooltip text-neutral-400 dark:text-neutral-500"
|
||||
data-tooltip={t(
|
||||
'branding.colorGroupSurfacesHelp',
|
||||
'The neutral layers behind your content. Background sits furthest back; Surface and Elevated stack on top.'
|
||||
)}
|
||||
tabIndex={0}
|
||||
>
|
||||
<Info className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
@@ -1057,11 +1062,12 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
<h4 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300 uppercase tracking-wide mb-3 flex items-center gap-1.5">
|
||||
{t('branding.colorGroupText', 'Text')}
|
||||
<span
|
||||
className="cursor-help text-neutral-400 dark:text-neutral-500"
|
||||
title={t(
|
||||
className="info-tooltip text-neutral-400 dark:text-neutral-500"
|
||||
data-tooltip={t(
|
||||
'branding.colorGroupTextHelp',
|
||||
'Foreground text colours. Primary is for everything readers focus on; Secondary is for supporting copy.'
|
||||
)}
|
||||
tabIndex={0}
|
||||
>
|
||||
<Info className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
@@ -1098,11 +1104,12 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
<h4 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300 uppercase tracking-wide mb-3 flex items-center gap-1.5">
|
||||
{t('branding.colorGroupAccent', 'Accent')}
|
||||
<span
|
||||
className="cursor-help text-neutral-400 dark:text-neutral-500"
|
||||
title={t(
|
||||
className="info-tooltip text-neutral-400 dark:text-neutral-500"
|
||||
data-tooltip={t(
|
||||
'branding.colorGroupAccentHelp',
|
||||
'Brand colours that highlight interactive elements. Use a strong colour pair — Accent is for outlines/text, Accent Dark is for filled buttons.'
|
||||
)}
|
||||
tabIndex={0}
|
||||
>
|
||||
<Info className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
@@ -1293,7 +1300,7 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
onClick={() => onCssTemplateChange(null)}
|
||||
className={`relative p-4 rounded-lg border-2 transition-all text-left ${
|
||||
!cssTemplateId
|
||||
? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
|
||||
? 'tile-selected'
|
||||
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
|
||||
}`}
|
||||
>
|
||||
@@ -1315,7 +1322,7 @@ export const ThemeCustomizerEnhanced: React.FC<ThemeCustomizerEnhancedProps> = (
|
||||
onClick={() => onCssTemplateChange(template.id)}
|
||||
className={`relative p-4 rounded-lg border-2 transition-all text-left ${
|
||||
cssTemplateId === template.id
|
||||
? 'border-accent-dark bg-primary-50 dark:bg-primary-900/30'
|
||||
? 'tile-selected'
|
||||
: 'border-neutral-200 dark:border-neutral-700 hover:border-neutral-300 dark:hover:border-neutral-600'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -495,7 +495,7 @@ export const GalleryLayout: React.FC<GalleryLayoutProps> = ({
|
||||
{t('gallery.needHelp')}{' '}
|
||||
<a
|
||||
href={`mailto:${brandingSettings.support_email}`}
|
||||
className="text-primary-600 hover:text-primary-700 break-all"
|
||||
className="text-accent hover:opacity-80 break-all"
|
||||
>
|
||||
{brandingSettings.support_email}
|
||||
</a>
|
||||
|
||||
@@ -255,7 +255,7 @@ export const GallerySidebar: React.FC<GallerySidebarProps> = ({
|
||||
className={`
|
||||
gallery-btn w-full text-left px-3 py-2 rounded-lg transition-colors flex items-center justify-between
|
||||
${selectedCategoryId === null
|
||||
? 'bg-primary-600/20 text-primary-500'
|
||||
? 'bg-accent-dark text-white'
|
||||
: 'hover:bg-black/10 text-muted-theme'
|
||||
}
|
||||
`}
|
||||
@@ -278,7 +278,7 @@ export const GallerySidebar: React.FC<GallerySidebarProps> = ({
|
||||
className={`
|
||||
gallery-btn w-full text-left px-3 py-2 rounded-lg transition-colors flex items-center justify-between
|
||||
${isSelected
|
||||
? 'bg-primary-600/20 text-primary-500'
|
||||
? 'bg-accent-dark text-white'
|
||||
: 'hover:bg-black/10 text-muted-theme'
|
||||
}
|
||||
`}
|
||||
@@ -362,7 +362,7 @@ export const GallerySidebar: React.FC<GallerySidebarProps> = ({
|
||||
className={`
|
||||
gallery-btn w-full text-left px-3 py-2 rounded-lg transition-colors flex items-center gap-3
|
||||
${isSelected
|
||||
? 'bg-primary-600/20 text-primary-500'
|
||||
? 'bg-accent-dark text-white'
|
||||
: 'hover:bg-black/10 text-muted-theme'
|
||||
}
|
||||
`}
|
||||
|
||||
@@ -145,7 +145,7 @@ export const GuestNamePromptModal: React.FC<GuestNamePromptModalProps> = ({
|
||||
closePrompt();
|
||||
openRecovery();
|
||||
}}
|
||||
className="text-sm text-primary-600 hover:underline w-full text-center pt-2"
|
||||
className="text-sm text-accent hover:underline w-full text-center pt-2"
|
||||
>
|
||||
{t('gallery.guestPrompt.alreadyHere', "I've been here before")}
|
||||
</button>
|
||||
|
||||
@@ -97,7 +97,7 @@ export const PhotoFilterBar: React.FC<PhotoFilterBarProps> = ({
|
||||
setShowSortMenu(false);
|
||||
}}
|
||||
className={`w-full text-left px-4 py-2 text-sm hover:bg-black/10 ${
|
||||
sortBy === 'date' ? 'text-primary-600 bg-primary-50' : 'text-muted-theme'
|
||||
sortBy === 'date' ? 'bg-accent-dark text-white' : 'text-muted-theme'
|
||||
}`}
|
||||
>
|
||||
{t('gallery.sortByDate')}
|
||||
@@ -108,7 +108,7 @@ export const PhotoFilterBar: React.FC<PhotoFilterBarProps> = ({
|
||||
setShowSortMenu(false);
|
||||
}}
|
||||
className={`w-full text-left px-4 py-2 text-sm hover:bg-black/10 ${
|
||||
sortBy === 'capture_date' ? 'text-primary-600 bg-primary-50' : 'text-muted-theme'
|
||||
sortBy === 'capture_date' ? 'bg-accent-dark text-white' : 'text-muted-theme'
|
||||
}`}
|
||||
>
|
||||
{t('photoSort.dateTaken', 'Date Taken')}
|
||||
@@ -119,7 +119,7 @@ export const PhotoFilterBar: React.FC<PhotoFilterBarProps> = ({
|
||||
setShowSortMenu(false);
|
||||
}}
|
||||
className={`w-full text-left px-4 py-2 text-sm hover:bg-black/10 ${
|
||||
sortBy === 'name' ? 'text-primary-600 bg-primary-50' : 'text-muted-theme'
|
||||
sortBy === 'name' ? 'bg-accent-dark text-white' : 'text-muted-theme'
|
||||
}`}
|
||||
>
|
||||
{t('gallery.sortByName')}
|
||||
@@ -130,7 +130,7 @@ export const PhotoFilterBar: React.FC<PhotoFilterBarProps> = ({
|
||||
setShowSortMenu(false);
|
||||
}}
|
||||
className={`w-full text-left px-4 py-2 text-sm hover:bg-black/10 ${
|
||||
sortBy === 'size' ? 'text-primary-600 bg-primary-50' : 'text-muted-theme'
|
||||
sortBy === 'size' ? 'bg-accent-dark text-white' : 'text-muted-theme'
|
||||
}`}
|
||||
>
|
||||
{t('gallery.sortBySize')}
|
||||
@@ -141,7 +141,7 @@ export const PhotoFilterBar: React.FC<PhotoFilterBarProps> = ({
|
||||
setShowSortMenu(false);
|
||||
}}
|
||||
className={`w-full text-left px-4 py-2 text-sm hover:bg-black/10 ${
|
||||
sortBy === 'rating' ? 'text-primary-600 bg-primary-50' : 'text-muted-theme'
|
||||
sortBy === 'rating' ? 'bg-accent-dark text-white' : 'text-muted-theme'
|
||||
}`}
|
||||
>
|
||||
{t('gallery.sortByRating', 'Sort by Rating')}
|
||||
|
||||
@@ -298,6 +298,73 @@
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
/*
|
||||
* Selected-tile state for picker grids (Gallery Layout, Filter Bar Style,
|
||||
* Header Style, Hero Divider, Theme Presets). Used in place of the dim
|
||||
* "border + light tint + accent text" pattern which had poor contrast on
|
||||
* dark themes (accent text on dim accent bg). The full accent-dark fill
|
||||
* with white descendants gives a strong, accessible selection cue and
|
||||
* follows the user's CI palette.
|
||||
*/
|
||||
.tile-selected {
|
||||
background-color: var(--color-accent-dark) !important;
|
||||
border-color: var(--color-accent-dark) !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.tile-selected *,
|
||||
.tile-selected svg {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/*
|
||||
* Inline hover tooltip used by the colour-picker info icons.
|
||||
* Pure CSS — no library, no JS state. Wrap an `<Info>` icon (or any
|
||||
* trigger) in <span class="info-tooltip" data-tooltip="..."> and a
|
||||
* positioned bubble fades in on hover/focus. The native HTML `title`
|
||||
* attribute has a ~1.5s delay and is suppressed in some browsers, which
|
||||
* is why earlier iterations appeared not to work for users.
|
||||
*/
|
||||
.info-tooltip {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.info-tooltip::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: calc(100% + 6px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 50;
|
||||
min-width: 200px;
|
||||
max-width: 320px;
|
||||
padding: 0.5rem 0.625rem;
|
||||
border-radius: 0.375rem;
|
||||
background-color: #171717;
|
||||
color: #fafafa;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.35;
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
white-space: normal;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 120ms ease-out;
|
||||
}
|
||||
|
||||
.info-tooltip:hover::after,
|
||||
.info-tooltip:focus-visible::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dark .info-tooltip::after {
|
||||
background-color: #fafafa;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
/* Image loading skeleton */
|
||||
.skeleton {
|
||||
@apply animate-pulse rounded-lg bg-neutral-200;
|
||||
|
||||
Reference in New Issue
Block a user