mirror of
https://github.com/block/buzz.git
synced 2026-11-02 15:31:27 +00:00
feat(desktop): icon mode selector, wrapping grid, light-on-top SVG
- Replace preview thumbnails in mode selector with Lucide icons (SunMoon for System, Sun for Light, Moon for Dark) - Switch from horizontal scroll to wrapping grid for theme list (since mode selector already filters the themes) - Remove HorizontalScrollWithFade component (no longer needed) - Remove section subtitles — just the mode buttons - Swap SystemPreferencePreviewSvg: light on top, dark on bottom - Remove unused expanded state and Show all toggle
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { type ReactNode, useMemo, useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import {
|
||||
BellRing,
|
||||
Bot,
|
||||
@@ -10,9 +10,12 @@ import {
|
||||
LayoutTemplate,
|
||||
LockKeyhole,
|
||||
MonitorCog,
|
||||
Moon,
|
||||
Smartphone,
|
||||
Smile,
|
||||
Stethoscope,
|
||||
Sun,
|
||||
SunMoon,
|
||||
UserRound,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
@@ -330,35 +333,6 @@ function SingleThemeTile({
|
||||
);
|
||||
}
|
||||
|
||||
function HorizontalScrollWithFade({
|
||||
children,
|
||||
expanded,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
expanded: boolean;
|
||||
}) {
|
||||
if (expanded) {
|
||||
return <div className="flex flex-wrap gap-3 p-1">{children}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="flex gap-3 overflow-x-auto p-1 pb-2 [scrollbar-width:thin]">
|
||||
{children}
|
||||
</div>
|
||||
{/* Right fade */}
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute inset-y-0 right-0 w-8"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(to left, hsl(var(--background)), hsl(var(--background) / 0))",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type AppearanceMode = "system" | "light" | "dark";
|
||||
|
||||
function ThemeSettingsCard() {
|
||||
@@ -383,7 +357,6 @@ function ThemeSettingsCard() {
|
||||
: "light";
|
||||
|
||||
const [selectedMode, setSelectedMode] = useState<AppearanceMode>(activeMode);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
const getVars = (name: SyntaxThemeName) =>
|
||||
withAccentPreviewVars(
|
||||
@@ -407,7 +380,6 @@ function ThemeSettingsCard() {
|
||||
|
||||
const handleModeSelect = (mode: AppearanceMode) => {
|
||||
setSelectedMode(mode);
|
||||
setExpanded(false);
|
||||
};
|
||||
|
||||
const handleSelectTheme = (name: SyntaxThemeName) => {
|
||||
@@ -436,18 +408,18 @@ function ThemeSettingsCard() {
|
||||
/>
|
||||
|
||||
{/* Mode selector: System / Light / Dark */}
|
||||
<div className="mb-6 flex gap-3">
|
||||
<div className="mb-8 flex gap-2">
|
||||
{(
|
||||
[
|
||||
{ mode: "system" as const, label: "System" },
|
||||
{ mode: "light" as const, label: "Light" },
|
||||
{ mode: "dark" as const, label: "Dark" },
|
||||
{ mode: "system" as const, label: "System", Icon: SunMoon },
|
||||
{ mode: "light" as const, label: "Light", Icon: Sun },
|
||||
{ mode: "dark" as const, label: "Dark", Icon: Moon },
|
||||
] as const
|
||||
).map(({ mode, label }) => (
|
||||
).map(({ mode, label, Icon }) => (
|
||||
<button
|
||||
aria-pressed={selectedMode === mode}
|
||||
className={cn(
|
||||
"flex flex-col items-center gap-1.5 rounded-2xl border px-5 py-3 text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",
|
||||
"flex items-center gap-2 rounded-lg border px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",
|
||||
selectedMode === mode
|
||||
? "border-primary bg-primary/10 text-foreground"
|
||||
: "border-border/70 text-muted-foreground hover:border-border hover:text-foreground",
|
||||
@@ -457,52 +429,15 @@ function ThemeSettingsCard() {
|
||||
onClick={() => handleModeSelect(mode)}
|
||||
type="button"
|
||||
>
|
||||
{mode === "system" && (
|
||||
<SystemPreferencePreviewFrame
|
||||
className="h-12 w-16"
|
||||
darkVars={getVars(
|
||||
pairedLight[0]
|
||||
? (getThemePair(pairedLight[0]) ?? pairedLight[0])
|
||||
: pairedLight[0],
|
||||
)}
|
||||
lightVars={getVars(pairedLight[0])}
|
||||
/>
|
||||
)}
|
||||
{mode === "light" && (
|
||||
<ThemePreviewFrame
|
||||
className="h-12 w-16"
|
||||
vars={getVars(allLightThemes[0])}
|
||||
/>
|
||||
)}
|
||||
{mode === "dark" && (
|
||||
<ThemePreviewFrame
|
||||
className="h-12 w-16"
|
||||
vars={getVars(allDarkThemes[0])}
|
||||
/>
|
||||
)}
|
||||
<Icon className="h-4 w-4" />
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Theme list based on selected mode */}
|
||||
{/* Theme grid based on selected mode */}
|
||||
<div className="mb-6">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium text-foreground">Themes</h3>
|
||||
{((selectedMode === "system" && pairedLight.length > 4) ||
|
||||
(selectedMode === "light" && allLightThemes.length > 4) ||
|
||||
(selectedMode === "dark" && allDarkThemes.length > 4)) && (
|
||||
<button
|
||||
className="text-2xs text-muted-foreground hover:text-foreground"
|
||||
onClick={() => setExpanded((v) => !v)}
|
||||
type="button"
|
||||
>
|
||||
{expanded ? "Collapse" : "Show all"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<HorizontalScrollWithFade expanded={expanded}>
|
||||
<div className="flex flex-wrap gap-3 p-1">
|
||||
{selectedMode === "system" &&
|
||||
pairedLight.map((lightName) => {
|
||||
const darkName = getThemePair(lightName);
|
||||
@@ -538,7 +473,7 @@ function ThemeSettingsCard() {
|
||||
vars={getVars(name)}
|
||||
/>
|
||||
))}
|
||||
</HorizontalScrollWithFade>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Accent color picker */}
|
||||
|
||||
@@ -233,12 +233,12 @@ function SystemPreferencePreviewSvg({
|
||||
viewBox="0 0 118 80"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
{/* Dark half (top) */}
|
||||
<g clipPath={`url(#${clipDark})`}>
|
||||
<rect fill={darkBg} height="180" rx="3.6" width="288" />
|
||||
<rect fill={darkSidebar} height="180" width="57.375" />
|
||||
{/* Light half (top) */}
|
||||
<g clipPath={`url(#${clipLight})`}>
|
||||
<rect fill={lightBg} height="180" rx="3.6" width="288" />
|
||||
<rect fill={lightSidebar} height="180" width="57.375" />
|
||||
<rect
|
||||
fill={darkSidebarFg}
|
||||
fill={lightSidebarFg}
|
||||
height="3.6"
|
||||
rx="0.9"
|
||||
width="3.6"
|
||||
@@ -246,7 +246,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="15.9751"
|
||||
/>
|
||||
<rect
|
||||
fill={darkSidebarFg}
|
||||
fill={lightSidebarFg}
|
||||
height="3.6"
|
||||
rx="0.9"
|
||||
width="3.6"
|
||||
@@ -254,7 +254,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="21.375"
|
||||
/>
|
||||
<rect
|
||||
fill={darkSidebarFg}
|
||||
fill={lightSidebarFg}
|
||||
height="3.6"
|
||||
rx="0.9"
|
||||
width="3.6"
|
||||
@@ -262,7 +262,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="26.7749"
|
||||
/>
|
||||
<rect
|
||||
fill={darkSidebarFg}
|
||||
fill={lightSidebarFg}
|
||||
height="3.6"
|
||||
rx="0.9"
|
||||
width="3.6"
|
||||
@@ -324,7 +324,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="4.7811"
|
||||
/>
|
||||
<rect
|
||||
fill={darkSidebarFg}
|
||||
fill={lightSidebarFg}
|
||||
height="1.8"
|
||||
rx="0.225"
|
||||
width="45.225"
|
||||
@@ -332,7 +332,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="16.875"
|
||||
/>
|
||||
<rect
|
||||
fill={darkSidebarFg}
|
||||
fill={lightSidebarFg}
|
||||
height="1.8"
|
||||
rx="0.225"
|
||||
width="45.225"
|
||||
@@ -340,7 +340,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="22.2749"
|
||||
/>
|
||||
<rect
|
||||
fill={darkSidebarFg}
|
||||
fill={lightSidebarFg}
|
||||
height="1.8"
|
||||
rx="0.225"
|
||||
width="45.225"
|
||||
@@ -348,7 +348,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="27.675"
|
||||
/>
|
||||
<rect
|
||||
fill={darkSidebarFg}
|
||||
fill={lightSidebarFg}
|
||||
height="1.8"
|
||||
rx="0.225"
|
||||
width="45.225"
|
||||
@@ -356,7 +356,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="33.075"
|
||||
/>
|
||||
<rect
|
||||
fill={darkMutedFg}
|
||||
fill={lightMutedFg}
|
||||
height="1.8"
|
||||
rx="0.225"
|
||||
width="26.775"
|
||||
@@ -365,13 +365,13 @@ function SystemPreferencePreviewSvg({
|
||||
/>
|
||||
</g>
|
||||
|
||||
{/* Light half (bottom) — clipped to bottom portion */}
|
||||
<g clipPath={`url(#${clipLight})`}>
|
||||
{/* Dark half (bottom) — clipped to bottom portion */}
|
||||
<g clipPath={`url(#${clipDark})`}>
|
||||
<g clipPath={`url(#${clipOuter})`}>
|
||||
<rect fill={lightBg} height="180" rx="3.6" width="288" y="22" />
|
||||
<rect fill={lightSidebar} height="180" width="57.375" y="22" />
|
||||
<rect fill={darkBg} height="180" rx="3.6" width="288" y="22" />
|
||||
<rect fill={darkSidebar} height="180" width="57.375" y="22" />
|
||||
<rect
|
||||
fill={lightSidebarFg}
|
||||
fill={darkSidebarFg}
|
||||
height="3.6"
|
||||
rx="0.9"
|
||||
width="3.6"
|
||||
@@ -379,7 +379,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="37.9751"
|
||||
/>
|
||||
<rect
|
||||
fill={lightSidebarFg}
|
||||
fill={darkSidebarFg}
|
||||
height="3.6"
|
||||
rx="0.9"
|
||||
width="3.6"
|
||||
@@ -387,7 +387,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="43.375"
|
||||
/>
|
||||
<rect
|
||||
fill={lightSidebarFg}
|
||||
fill={darkSidebarFg}
|
||||
height="3.6"
|
||||
rx="0.9"
|
||||
width="3.6"
|
||||
@@ -395,7 +395,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="48.7749"
|
||||
/>
|
||||
<rect
|
||||
fill={lightSidebarFg}
|
||||
fill={darkSidebarFg}
|
||||
height="3.6"
|
||||
rx="0.9"
|
||||
width="3.6"
|
||||
@@ -403,7 +403,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="54.175"
|
||||
/>
|
||||
<rect
|
||||
fill={lightSidebarFg}
|
||||
fill={darkSidebarFg}
|
||||
height="1.8"
|
||||
rx="0.225"
|
||||
width="45.225"
|
||||
@@ -411,7 +411,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="38.875"
|
||||
/>
|
||||
<rect
|
||||
fill={lightSidebarFg}
|
||||
fill={darkSidebarFg}
|
||||
height="1.8"
|
||||
rx="0.225"
|
||||
width="45.225"
|
||||
@@ -419,7 +419,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="44.2749"
|
||||
/>
|
||||
<rect
|
||||
fill={lightSidebarFg}
|
||||
fill={darkSidebarFg}
|
||||
height="1.8"
|
||||
rx="0.225"
|
||||
width="45.225"
|
||||
@@ -427,7 +427,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="49.675"
|
||||
/>
|
||||
<rect
|
||||
fill={lightSidebarFg}
|
||||
fill={darkSidebarFg}
|
||||
height="1.8"
|
||||
rx="0.225"
|
||||
width="45.225"
|
||||
@@ -435,7 +435,7 @@ function SystemPreferencePreviewSvg({
|
||||
y="55.075"
|
||||
/>
|
||||
<rect
|
||||
fill={lightMutedFg}
|
||||
fill={darkMutedFg}
|
||||
height="1.8"
|
||||
rx="0.225"
|
||||
width="26.775"
|
||||
@@ -446,10 +446,10 @@ function SystemPreferencePreviewSvg({
|
||||
</g>
|
||||
|
||||
<defs>
|
||||
<clipPath id={clipDark}>
|
||||
<clipPath id={clipLight}>
|
||||
<rect fill="white" height="180" rx="3.6" width="288" />
|
||||
</clipPath>
|
||||
<clipPath id={clipLight}>
|
||||
<clipPath id={clipDark}>
|
||||
<path d="M0 37H118V80H0V37Z" fill="white" />
|
||||
</clipPath>
|
||||
<clipPath id={clipOuter}>
|
||||
|
||||
Reference in New Issue
Block a user