fix: isolate shared admin styling
This commit is contained in:
1 parent
4ce35e91fb
commit
0fe482009c
15 files changed
+157
-150
No files matched your search
+12
-12
@@ -158,7 +158,7 @@ export default async function AdminLayout({ children }: { children: ReactNode })
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-[260px_minmax(0,1fr)] min-h-screen bg-[var(--color-background)]">
|
<div className="grid grid-cols-1 lg:grid-cols-[260px_minmax(0,1fr)] min-h-screen bg-[var(--admin-canvas)]">
|
||||||
<Sidebar staff={staff} />
|
<Sidebar staff={staff} />
|
||||||
<div className="flex flex-col min-w-0 p-5 lg:p-6">
|
<div className="flex flex-col min-w-0 p-5 lg:p-6">
|
||||||
<Topbar staff={staff} />
|
<Topbar staff={staff} />
|
||||||
@@ -175,9 +175,9 @@ async function Sidebar({ staff }: { staff: { id: number; username: string; rank:
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="sticky top-0 self-start h-screen bg-[var(--admin-sidebar-background)] text-[var(--admin-sidebar-text-readable)] shadow-xl flex flex-col">
|
<aside className="sticky top-0 self-start h-screen bg-[var(--admin-sidebar-background)] text-[var(--admin-sidebar-text-readable)] shadow-xl flex flex-col">
|
||||||
<div className="flex items-center gap-3 px-4 py-5 border-b border-[var(--border-subtle)]">
|
<div className="flex items-center gap-3 px-4 py-5 border-b border-[var(--admin-border)]">
|
||||||
<span
|
<span
|
||||||
className="flex-none w-10 h-10 rounded-xl grid place-items-center font-extrabold text-base text-[var(--color-primary-foreground-readable)] bg-[var(--color-primary)] shadow-lg shadow-[var(--color-primary)]/20"
|
className="flex-none w-10 h-10 rounded-xl grid place-items-center font-extrabold text-base text-[var(--color-primary-foreground-readable)] bg-[var(--admin-accent)] shadow-lg shadow-[var(--admin-accent)]/20"
|
||||||
aria-hidden
|
aria-hidden
|
||||||
>
|
>
|
||||||
{initial}
|
{initial}
|
||||||
@@ -208,10 +208,10 @@ async function Sidebar({ staff }: { staff: { id: number; username: string; rank:
|
|||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div className="px-3 py-3 border-t border-[var(--border-subtle)]">
|
<div className="px-3 py-3 border-t border-[var(--admin-border)]">
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
className="flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-[var(--admin-sidebar-muted-readable)] text-xs font-medium hover:text-[var(--admin-sidebar-text-readable)] hover:bg-[var(--color-primary)]/10 transition-all duration-150 no-underline"
|
className="flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-[var(--admin-sidebar-muted-readable)] text-xs font-medium hover:text-[var(--admin-sidebar-text-readable)] hover:bg-[var(--admin-accent)]/10 transition-all duration-150 no-underline"
|
||||||
>
|
>
|
||||||
<LogOut size={14} />
|
<LogOut size={14} />
|
||||||
<span>{t("backToSite")}</span>
|
<span>{t("backToSite")}</span>
|
||||||
@@ -225,19 +225,19 @@ async function Topbar({ staff }: { staff: { id: number; username: string; rank:
|
|||||||
const t = await getTranslations("pages.admin.nav");
|
const t = await getTranslations("pages.admin.nav");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="flex items-center justify-between gap-4 bg-[var(--color-surface)] border border-[var(--border-subtle)] rounded-2xl px-5 py-3 mb-5 shadow-card">
|
<header className="flex items-center justify-between gap-4 bg-[var(--admin-surface)] border border-[var(--admin-border)] rounded-2xl px-5 py-3 mb-5 shadow-card">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<h2 className="m-0 text-base font-extrabold text-[var(--color-text)]">{t("housekeeping")}</h2>
|
<h2 className="m-0 text-base font-extrabold text-[var(--admin-text)]">{t("housekeeping")}</h2>
|
||||||
<span className="hidden sm:inline-block w-px h-5 bg-[var(--border-subtle)]" />
|
<span className="hidden sm:inline-block w-px h-5 bg-[var(--admin-border)]" />
|
||||||
<span className="hidden sm:inline text-xs text-[var(--color-text-muted)]">
|
<span className="hidden sm:inline text-xs text-[var(--admin-text-muted)]">
|
||||||
{t("hotelManagementPanel")}
|
{t("hotelManagementPanel")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-xs text-[var(--color-text-muted)]">
|
<span className="text-xs text-[var(--admin-text-muted)]">
|
||||||
<strong className="text-[var(--color-text)]">{staff.username}</strong>
|
<strong className="text-[var(--admin-text)]">{staff.username}</strong>
|
||||||
</span>
|
</span>
|
||||||
<span className="inline-flex items-center gap-1.5 text-[0.6rem] font-bold uppercase tracking-wider text-[var(--color-primary-foreground-readable)] bg-[var(--color-primary)] rounded-full px-2.5 py-1">
|
<span className="inline-flex items-center gap-1.5 text-[0.6rem] font-bold uppercase tracking-wider text-[var(--color-primary-foreground-readable)] bg-[var(--admin-accent)] rounded-full px-2.5 py-1">
|
||||||
{t("rankLabel", { rank: staff.rank })}
|
{t("rankLabel", { rank: staff.rank })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+66
-66
File diff suppressed because it is too large.
Load diff
@@ -27,8 +27,8 @@ export function AdminNavLink({
|
|||||||
href={href}
|
href={href}
|
||||||
className={`flex items-center gap-2.5 px-2.5 py-2 rounded-xl text-sm font-medium no-underline transition-all duration-150 ${
|
className={`flex items-center gap-2.5 px-2.5 py-2 rounded-xl text-sm font-medium no-underline transition-all duration-150 ${
|
||||||
active
|
active
|
||||||
? "bg-[var(--color-primary)]/15 text-[var(--admin-sidebar-text-readable)] shadow-sm border-l-2 border-[var(--color-primary)]"
|
? "bg-[var(--admin-accent)]/15 text-[var(--admin-sidebar-text-readable)] shadow-sm border-l-2 border-[var(--admin-accent)]"
|
||||||
: "text-[var(--admin-sidebar-muted-readable)] hover:text-[var(--admin-sidebar-text-readable)] hover:bg-[var(--color-primary)]/10 hover:no-underline"
|
: "text-[var(--admin-sidebar-muted-readable)] hover:text-[var(--admin-sidebar-text-readable)] hover:bg-[var(--admin-accent)]/10 hover:no-underline"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{icon ? (
|
{icon ? (
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
|
|||||||
{isDirty && (
|
{isDirty && (
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="h-4 px-1.5 text-[9px] text-[var(--color-warning-text-readable)] border-[var(--color-warning-border)] bg-[var(--color-warning-subtle)]"
|
className="h-4 px-1.5 text-[9px] text-[var(--admin-warning)] border-[var(--admin-warning-border)] bg-[var(--admin-warning-subtle)]"
|
||||||
>
|
>
|
||||||
<AlertCircle className="h-2.5 w-2.5 mr-0.5" />
|
<AlertCircle className="h-2.5 w-2.5 mr-0.5" />
|
||||||
unsaved
|
unsaved
|
||||||
@@ -310,7 +310,7 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
|
|||||||
|
|
||||||
{/* Dirty banner */}
|
{/* Dirty banner */}
|
||||||
{isDirty && (
|
{isDirty && (
|
||||||
<div className="flex items-center gap-2 px-4 py-1.5 bg-[var(--color-warning-subtle)] border-b border-[var(--color-warning-border)] text-xs text-[var(--color-warning-text-readable)] shrink-0">
|
<div className="flex items-center gap-2 px-4 py-1.5 bg-[var(--admin-warning-subtle)] border-b border-[var(--admin-warning-border)] text-xs text-[var(--admin-warning)] shrink-0">
|
||||||
<AlertCircle className="h-3.5 w-3.5 shrink-0" />
|
<AlertCircle className="h-3.5 w-3.5 shrink-0" />
|
||||||
<span className="flex-1">
|
<span className="flex-1">
|
||||||
Unsaved changes — press{' '}
|
Unsaved changes — press{' '}
|
||||||
@@ -340,7 +340,7 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
|
|||||||
<TabsTrigger value="settings" className="gap-1.5 text-xs">
|
<TabsTrigger value="settings" className="gap-1.5 text-xs">
|
||||||
<Settings className="h-3.5 w-3.5" />
|
<Settings className="h-3.5 w-3.5" />
|
||||||
Settings
|
Settings
|
||||||
{isDirty && <span className="ml-0.5 h-1.5 w-1.5 rounded-full bg-[var(--color-warning)]" />}
|
{isDirty && <span className="ml-0.5 h-1.5 w-1.5 rounded-full bg-[var(--admin-warning)]" />}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="items" className="gap-1.5 text-xs">
|
<TabsTrigger value="items" className="gap-1.5 text-xs">
|
||||||
<Package className="h-3.5 w-3.5" />
|
<Package className="h-3.5 w-3.5" />
|
||||||
@@ -359,7 +359,7 @@ export function InlineEditor({ pageId, onSaved }: InlineEditorProps) {
|
|||||||
const furniSet = new Set(itemsData.furniDataIdList)
|
const furniSet = new Set(itemsData.furniDataIdList)
|
||||||
const missing = itemsData.baseItems.filter((b) => !furniSet.has(b.id)).length
|
const missing = itemsData.baseItems.filter((b) => !furniSet.has(b.id)).length
|
||||||
return missing > 0 ? (
|
return missing > 0 ? (
|
||||||
<span className="ml-0.5 rounded-full bg-[var(--color-warning)] text-[var(--color-warning-foreground-readable)] text-[9px] px-1 py-0.5 leading-none font-medium">
|
<span className="ml-0.5 rounded-full bg-[var(--admin-warning)] text-[var(--admin-warning-foreground)] text-[9px] px-1 py-0.5 leading-none font-medium">
|
||||||
{missing}
|
{missing}
|
||||||
</span>
|
</span>
|
||||||
) : null
|
) : null
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export const TreeItemContent = React.memo(
|
|||||||
!sortMode && 'cursor-pointer',
|
!sortMode && 'cursor-pointer',
|
||||||
isSelected && !isDragging && 'bg-primary/10 ring-1 ring-primary/30',
|
isSelected && !isDragging && 'bg-primary/10 ring-1 ring-primary/30',
|
||||||
isMultiSelected && !isSelected && !isDragging && 'bg-primary/5 ring-1 ring-primary/15',
|
isMultiSelected && !isSelected && !isDragging && 'bg-primary/5 ring-1 ring-primary/15',
|
||||||
isFocused && !isSelected && !isMultiSelected && 'ring-2 ring-blue-500/40',
|
isFocused && !isSelected && !isMultiSelected && 'ring-2 ring-[var(--admin-focus-ring)]/40',
|
||||||
!isSelected && !isMultiSelected && !isFocused && !isDragging && 'hover:bg-accent/60',
|
!isSelected && !isMultiSelected && !isFocused && !isDragging && 'hover:bg-accent/60',
|
||||||
isAncestorOfSelected &&
|
isAncestorOfSelected &&
|
||||||
!isSelected &&
|
!isSelected &&
|
||||||
|
|||||||
@@ -109,17 +109,17 @@ export function CatalogIcon({ iconImage, size = 20 }: { iconImage: number; size?
|
|||||||
/* ─── Layout label helper ────────────────────────────────── */
|
/* ─── Layout label helper ────────────────────────────────── */
|
||||||
|
|
||||||
export const LAYOUT_COLORS: Record<string, string> = {
|
export const LAYOUT_COLORS: Record<string, string> = {
|
||||||
default_3x3: 'bg-[var(--color-info-subtle)] text-[var(--color-info-text-readable)] dark:text-[var(--color-info-text-readable)]',
|
default_3x3: 'bg-[var(--admin-info-subtle)] text-[var(--admin-info)] dark:text-[var(--admin-info)]',
|
||||||
frontpage: 'bg-[var(--color-warning-subtle)] text-[var(--color-warning-text-readable)] dark:text-[var(--color-warning-text-readable)]',
|
frontpage: 'bg-[var(--admin-warning-subtle)] text-[var(--admin-warning)] dark:text-[var(--admin-warning)]',
|
||||||
spaces_new: 'bg-[var(--color-success-subtle)] text-[var(--color-success-text-readable)] dark:text-[var(--color-success-text-readable)]',
|
spaces_new: 'bg-[var(--admin-success-subtle)] text-[var(--admin-success)] dark:text-[var(--admin-success)]',
|
||||||
pets: 'bg-pink-500/15 text-pink-600 dark:text-pink-400',
|
pets: 'bg-[var(--admin-error)]/15 text-[var(--admin-error)]',
|
||||||
pets2: 'bg-pink-500/15 text-pink-600 dark:text-pink-400',
|
pets2: 'bg-[var(--admin-error)]/15 text-[var(--admin-error)]',
|
||||||
pets3: 'bg-pink-500/15 text-pink-600 dark:text-pink-400',
|
pets3: 'bg-[var(--admin-error)]/15 text-[var(--admin-error)]',
|
||||||
marketplace: 'bg-purple-500/15 text-purple-600 dark:text-purple-400',
|
marketplace: 'bg-[var(--admin-accent)]/15 text-[var(--admin-accent-text)]',
|
||||||
recycler: 'bg-[var(--color-success-subtle)] text-[var(--color-success-text-readable)] dark:text-[var(--color-success-text-readable)]',
|
recycler: 'bg-[var(--admin-success-subtle)] text-[var(--admin-success)] dark:text-[var(--admin-success)]',
|
||||||
club_buy: 'bg-[var(--color-warning-subtle)] text-[var(--color-warning-text-readable)] dark:text-[var(--color-warning-text-readable)]',
|
club_buy: 'bg-[var(--admin-warning-subtle)] text-[var(--admin-warning)] dark:text-[var(--admin-warning)]',
|
||||||
single_bundle: 'bg-[var(--color-info-subtle)] text-[var(--color-info-text-readable)] dark:text-[var(--color-info-text-readable)]',
|
single_bundle: 'bg-[var(--admin-info-subtle)] text-[var(--admin-info)] dark:text-[var(--admin-info)]',
|
||||||
room_bundle: 'bg-[var(--color-info-subtle)] text-[var(--color-info-text-readable)] dark:text-[var(--color-info-text-readable)]',
|
room_bundle: 'bg-[var(--admin-info-subtle)] text-[var(--admin-info)] dark:text-[var(--admin-info)]',
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── Highlight matched text ─────────────────────────────── */
|
/* ─── Highlight matched text ─────────────────────────────── */
|
||||||
@@ -131,7 +131,7 @@ function HighlightText({ text, query }: { text: string; query: string }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{text.slice(0, idx)}
|
{text.slice(0, idx)}
|
||||||
<mark className="bg-[var(--color-warning-subtle)] text-[var(--color-warning-text-readable)] rounded-sm px-0.5">
|
<mark className="bg-[var(--admin-warning-subtle)] text-[var(--admin-warning)] rounded-sm px-0.5">
|
||||||
{text.slice(idx, idx + query.length)}
|
{text.slice(idx, idx + query.length)}
|
||||||
</mark>
|
</mark>
|
||||||
{text.slice(idx + query.length)}
|
{text.slice(idx + query.length)}
|
||||||
@@ -325,7 +325,7 @@ export function CatalogTree({ rootPages, catalogType = 'normal' }: CatalogTreePr
|
|||||||
className={cn(
|
className={cn(
|
||||||
'px-2 py-1 rounded text-xs font-medium transition-colors flex items-center gap-1',
|
'px-2 py-1 rounded text-xs font-medium transition-colors flex items-center gap-1',
|
||||||
filterMode === 'enabled'
|
filterMode === 'enabled'
|
||||||
? 'bg-background shadow-sm text-[var(--color-success-text-readable)] dark:text-[var(--color-success-text-readable)]'
|
? 'bg-background shadow-sm text-[var(--admin-success)] dark:text-[var(--admin-success)]'
|
||||||
: 'text-muted-foreground hover:text-foreground',
|
: 'text-muted-foreground hover:text-foreground',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -345,7 +345,7 @@ export function CatalogTree({ rootPages, catalogType = 'normal' }: CatalogTreePr
|
|||||||
className={cn(
|
className={cn(
|
||||||
'px-2 py-1 rounded text-xs font-medium transition-colors flex items-center gap-1',
|
'px-2 py-1 rounded text-xs font-medium transition-colors flex items-center gap-1',
|
||||||
filterMode === 'disabled'
|
filterMode === 'disabled'
|
||||||
? 'bg-background shadow-sm text-[var(--color-error-text-readable)] dark:text-[var(--color-error-text-readable)]'
|
? 'bg-background shadow-sm text-[var(--admin-error)] dark:text-[var(--admin-error)]'
|
||||||
: 'text-muted-foreground hover:text-foreground',
|
: 'text-muted-foreground hover:text-foreground',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -643,8 +643,8 @@ function TreeItem({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center justify-center h-5 w-5 shrink-0 rounded opacity-0 group-hover:opacity-100 transition-opacity',
|
'flex items-center justify-center h-5 w-5 shrink-0 rounded opacity-0 group-hover:opacity-100 transition-opacity',
|
||||||
isDisabled
|
isDisabled
|
||||||
? 'hover:bg-[var(--color-success-subtle)] text-muted-foreground hover:text-[var(--color-success-text-readable)]'
|
? 'hover:bg-[var(--admin-success-subtle)] text-muted-foreground hover:text-[var(--admin-success)]'
|
||||||
: 'hover:bg-[var(--color-error-subtle)] text-muted-foreground hover:text-[var(--color-error-text-readable)]',
|
: 'hover:bg-[var(--admin-error-subtle)] text-muted-foreground hover:text-[var(--admin-error)]',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isDisabled ? (
|
{isDisabled ? (
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ export function BulkImportItems({ pageId, onImported }: BulkImportItemsProps) {
|
|||||||
<td className="px-2 py-0.5 font-mono">{row.pointsType ?? '—'}</td>
|
<td className="px-2 py-0.5 font-mono">{row.pointsType ?? '—'}</td>
|
||||||
<td className="px-2 py-0.5">
|
<td className="px-2 py-0.5">
|
||||||
{row.ok ? (
|
{row.ok ? (
|
||||||
<span className="text-[var(--color-success-text-readable)]">OK</span>
|
<span className="text-[var(--admin-success)]">OK</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-destructive">{row.error}</span>
|
<span className="text-destructive">{row.error}</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export function ItemsShopPreview({
|
|||||||
{/* Badges overlay */}
|
{/* Badges overlay */}
|
||||||
<div className="absolute right-1 top-1 flex flex-col gap-0.5">
|
<div className="absolute right-1 top-1 flex flex-col gap-0.5">
|
||||||
{isClub && (
|
{isClub && (
|
||||||
<Badge className="h-4 gap-0.5 border-0 bg-[var(--color-warning-subtle)] px-1 text-[8px] text-white">
|
<Badge className="h-4 gap-0.5 border-0 bg-[var(--admin-warning-subtle)] px-1 text-[8px] text-white">
|
||||||
<Crown className="h-2.5 w-2.5" />
|
<Crown className="h-2.5 w-2.5" />
|
||||||
HC
|
HC
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -110,7 +110,7 @@ export function ItemsShopPreview({
|
|||||||
</p>
|
</p>
|
||||||
<div className="mt-0.5 flex items-center gap-1.5 text-[10px] font-mono">
|
<div className="mt-0.5 flex items-center gap-1.5 text-[10px] font-mono">
|
||||||
{item.costCredits > 0 && (
|
{item.costCredits > 0 && (
|
||||||
<span className="text-[var(--color-warning-text-readable)] dark:text-[var(--color-warning-text-readable)]">
|
<span className="text-[var(--admin-warning)] dark:text-[var(--admin-warning)]">
|
||||||
{item.costCredits}c
|
{item.costCredits}c
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -292,7 +292,7 @@ export function EmulatorSettingsForm({
|
|||||||
<div
|
<div
|
||||||
key={setting.key}
|
key={setting.key}
|
||||||
className={`flex items-center gap-3 px-3 py-1.5 border-l-4 ${borderColor} ${
|
className={`flex items-center gap-3 px-3 py-1.5 border-l-4 ${borderColor} ${
|
||||||
isChanged ? 'bg-[var(--color-warning-subtle)] dark:bg-[var(--color-warning-subtle)]' : ''
|
isChanged ? 'bg-[var(--admin-warning-subtle)] dark:bg-[var(--admin-warning-subtle)]' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<code
|
<code
|
||||||
@@ -305,7 +305,7 @@ export function EmulatorSettingsForm({
|
|||||||
value={display}
|
value={display}
|
||||||
onChange={(e) => updateValue(setting.key, e.target.value)}
|
onChange={(e) => updateValue(setting.key, e.target.value)}
|
||||||
className={`flex-1 h-8 text-xs font-mono ${
|
className={`flex-1 h-8 text-xs font-mono ${
|
||||||
isChanged ? 'border-[var(--color-warning-border)]' : ''
|
isChanged ? 'border-[var(--admin-warning-border)]' : ''
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
{isChanged && (
|
{isChanged && (
|
||||||
|
|||||||
@@ -93,9 +93,9 @@ export function AdminMediaGrid() {
|
|||||||
{progress.current} / {progress.total}
|
{progress.current} / {progress.total}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-2 w-full bg-[var(--color-primary)]/10 rounded-full overflow-hidden">
|
<div className="h-2 w-full bg-[var(--admin-accent)]/10 rounded-full overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="h-full bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-primary-hover)] transition-all duration-300"
|
className="h-full bg-gradient-to-r from-[var(--admin-accent)] to-[var(--admin-accent)] transition-all duration-300"
|
||||||
style={{ width: `${(progress.current / progress.total) * 100}%` }}
|
style={{ width: `${(progress.current / progress.total) * 100}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,12 +111,12 @@ export function AdminMediaGrid() {
|
|||||||
{files.map((f) => (
|
{files.map((f) => (
|
||||||
<div
|
<div
|
||||||
key={f.name}
|
key={f.name}
|
||||||
className="border border-[var(--color-text-muted)]/14 rounded-[10px] overflow-hidden bg-[var(--color-background)]"
|
className="border border-[var(--admin-text-muted)]/14 rounded-[10px] overflow-hidden bg-[var(--admin-canvas)]"
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img src={validImageUrl(f.url)} alt={f.name} className="w-full h-[120px] object-cover block" />
|
<img src={validImageUrl(f.url)} alt={f.name} className="w-full h-[120px] object-cover block" />
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<p className="text-xs text-[var(--color-text-muted)] m-0 mb-1 overflow-hidden text-ellipsis whitespace-nowrap">
|
<p className="text-xs text-[var(--admin-text-muted)] m-0 mb-1 overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
{f.name}
|
{f.name}
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export function MediaPicker({ onSelect, current }: { onSelect: (url: string) =>
|
|||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
fontSize: "0.78rem",
|
fontSize: "0.78rem",
|
||||||
color: "var(--color-text-muted)",
|
color: "var(--admin-text-muted)",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
@@ -89,9 +89,9 @@ export function MediaPicker({ onSelect, current }: { onSelect: (url: string) =>
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
className="bg-[var(--color-surface)] text-[var(--color-text)] rounded-2xl w-[min(90vw,680px)] max-h-[80vh] flex flex-col shadow-xl"
|
className="bg-[var(--admin-surface)] text-[var(--admin-text)] rounded-2xl w-[min(90vw,680px)] max-h-[80vh] flex flex-col shadow-xl"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between px-4 py-3 border-b border-[var(--color-text-muted)]/14">
|
<div className="flex items-center justify-between px-4 py-3 border-b border-[var(--admin-text-muted)]/14">
|
||||||
<strong className="text-base">Media Library</strong>
|
<strong className="text-base">Media Library</strong>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<label className="btn btn-primary text-sm cursor-pointer">
|
<label className="btn btn-primary text-sm cursor-pointer">
|
||||||
@@ -112,9 +112,9 @@ export function MediaPicker({ onSelect, current }: { onSelect: (url: string) =>
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-auto p-3">
|
<div className="flex-1 overflow-auto p-3">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<p className="text-xs text-[var(--color-text-muted)] text-center py-8">Loading…</p>
|
<p className="text-xs text-[var(--admin-text-muted)] text-center py-8">Loading…</p>
|
||||||
) : files.length === 0 ? (
|
) : files.length === 0 ? (
|
||||||
<p className="text-xs text-[var(--color-text-muted)] text-center py-8">
|
<p className="text-xs text-[var(--admin-text-muted)] text-center py-8">
|
||||||
No images yet. Upload one above.
|
No images yet. Upload one above.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
@@ -127,12 +127,12 @@ export function MediaPicker({ onSelect, current }: { onSelect: (url: string) =>
|
|||||||
onSelect(f.url);
|
onSelect(f.url);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
className="border-2 border-transparent rounded-xl overflow-hidden cursor-pointer bg-[var(--color-background)] p-0 flex flex-col transition-all duration-150"
|
className="border-2 border-transparent rounded-xl overflow-hidden cursor-pointer bg-[var(--admin-canvas)] p-0 flex flex-col transition-all duration-150"
|
||||||
style={{ borderColor: f.url === current ? "var(--color-primary)" : "transparent" }}
|
style={{ borderColor: f.url === current ? "var(--admin-accent)" : "transparent" }}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
if (f.url !== current)
|
if (f.url !== current)
|
||||||
e.currentTarget.style.borderColor =
|
e.currentTarget.style.borderColor =
|
||||||
"color-mix(in srgb, var(--color-primary) 40%, transparent)";
|
"color-mix(in srgb, var(--admin-accent) 40%, transparent)";
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
if (f.url !== current) e.currentTarget.style.borderColor = "transparent";
|
if (f.url !== current) e.currentTarget.style.borderColor = "transparent";
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ interface AdminPageHeaderProps {
|
|||||||
export function AdminPageHeader({ icon: Icon, title, subtitle }: AdminPageHeaderProps) {
|
export function AdminPageHeader({ icon: Icon, title, subtitle }: AdminPageHeaderProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-3 mb-6">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[var(--color-primary)]/20 to-[var(--color-primary)]/5 grid place-items-center">
|
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[var(--admin-accent)]/20 to-[var(--admin-accent)]/5 grid place-items-center">
|
||||||
<Icon size={20} className="text-[var(--color-primary)]" />
|
<Icon size={20} className="text-[var(--admin-accent)]" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="m-0 text-xl font-extrabold text-[var(--color-text)]">{title}</h1>
|
<h1 className="m-0 text-xl font-extrabold text-[var(--admin-text)]">{title}</h1>
|
||||||
<p className="m-0 text-xs text-[var(--color-text-muted)] mt-0.5">{subtitle}</p>
|
<p className="m-0 text-xs text-[var(--admin-text-muted)] mt-0.5">{subtitle}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ describe("themePaletteCss", () => {
|
|||||||
"--admin-accent", "--admin-accent-foreground", "--admin-overlay",
|
"--admin-accent", "--admin-accent-foreground", "--admin-overlay",
|
||||||
"--admin-focus-ring", "--admin-success", "--admin-warning",
|
"--admin-focus-ring", "--admin-success", "--admin-warning",
|
||||||
"--admin-error", "--admin-info",
|
"--admin-error", "--admin-info",
|
||||||
|
"--admin-success-subtle", "--admin-warning-subtle",
|
||||||
|
"--admin-error-border", "--admin-info-foreground",
|
||||||
]) expect(css).toContain(variable);
|
]) expect(css).toContain(variable);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -65,6 +65,11 @@ export function themePaletteCss(selector: string, palette: ThemePalette): string
|
|||||||
`--admin-sidebar-text:${admin.sidebarText};`,
|
`--admin-sidebar-text:${admin.sidebarText};`,
|
||||||
`--admin-overlay:${admin.overlay};`,
|
`--admin-overlay:${admin.overlay};`,
|
||||||
`--admin-focus-ring:${admin.focusRing};`,
|
`--admin-focus-ring:${admin.focusRing};`,
|
||||||
|
...(["success", "warning", "error", "info"] as const).flatMap((status) => [
|
||||||
|
`--admin-${status}-foreground:${readableColor("#ffffff", [admin[status]])};`,
|
||||||
|
`--admin-${status}-subtle:color-mix(in srgb, ${admin[status]} 14%, ${admin.surface});`,
|
||||||
|
`--admin-${status}-border:color-mix(in srgb, ${admin[status]} 38%, transparent);`,
|
||||||
|
]),
|
||||||
...(["success", "warning", "error", "info"] as const).flatMap((status) => [
|
...(["success", "warning", "error", "info"] as const).flatMap((status) => [
|
||||||
`--color-${status}-text-readable:${semantic.byName[`${status}-text`]};`,
|
`--color-${status}-text-readable:${semantic.byName[`${status}-text`]};`,
|
||||||
`--color-${status}-foreground-readable:${semantic.byName[`${status}-solid`]};`,
|
`--color-${status}-foreground-readable:${semantic.byName[`${status}-solid`]};`,
|
||||||
|
|||||||
Reference in new issue
Block a user