refactor: remove macOS vibrancy support and unify glass surfaces
Drops the macOS vibrancy toggle, IPC, and related settings copy Updates dialogs, popovers, tooltips, and dropdowns to use shared glass styles Adds sticky header fade behavior to model picker and sidebar lists
This commit is contained in:
@@ -25,6 +25,8 @@ It is **not** a context-panel surface. It is not registered in
|
||||
`lib/surfaces/registry.ts`, has no rail icon, no tab, no persisted width and no
|
||||
resizer. It is a card floating inside the chat column — rounded border, faint
|
||||
fill, its own margin — rather than a docked pane flush against the window edge.
|
||||
When it overlays the transcript, it uses the shared `oc-glass-panel` surface;
|
||||
the inline card keeps its lighter, non-blurred fill instead.
|
||||
|
||||
## Placement
|
||||
|
||||
|
||||
@@ -178,13 +178,14 @@ export const WorkStatusPanel: React.FC<Props> = ({ sessionId, directory, visible
|
||||
// Beside the transcript the translucent fill reads as depth; on top
|
||||
// of it, message bubbles showed straight through the rows. Frosting
|
||||
// separates the two without going fully opaque.
|
||||
'bg-[var(--surface-muted)]/80 backdrop-blur-md',
|
||||
'oc-glass-panel',
|
||||
],
|
||||
// An empty card is a border around a settings icon, which reads as a
|
||||
// fault rather than as "nothing to report".
|
||||
renderedSections === 0 && 'border-transparent bg-transparent shadow-none',
|
||||
'motion-reduce:transition-none',
|
||||
'rounded-xl border border-[var(--interactive-border)] bg-[var(--surface-muted)]/40',
|
||||
'rounded-xl border border-[var(--interactive-border)]',
|
||||
!overlay && 'bg-[var(--surface-muted)]/40',
|
||||
// A lighter version of the composer's lift: the same shape, but this
|
||||
// card is taller, so the composer's spread reads as heavy here.
|
||||
'shadow-[0_2px_8px_-3px_rgb(0_0_0_/_0.08)]',
|
||||
|
||||
@@ -336,7 +336,7 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
|
||||
</Tooltip>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="w-[min(27rem,calc(100vw-2rem))] max-h-[75vh] overflow-y-auto bg-[var(--surface-elevated)] p-0"
|
||||
className="w-[min(27rem,calc(100vw-2rem))] max-h-[75vh] overflow-y-auto p-0"
|
||||
>
|
||||
{isDesktopApp ? (
|
||||
<div>
|
||||
|
||||
@@ -127,7 +127,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ isOpen, isMobile, children, cl
|
||||
ref={sidebarRef}
|
||||
className={cn(
|
||||
'relative flex h-full overflow-hidden border-r border-border will-change-[width] motion-reduce:transition-none',
|
||||
'bg-sidebar oc-vibrancy-surface',
|
||||
'bg-sidebar',
|
||||
!isOpen && 'border-r-0',
|
||||
className,
|
||||
)}
|
||||
|
||||
@@ -28,7 +28,6 @@ const ICON_BUTTON_CLASS =
|
||||
export const TitlebarLeftControls: React.FC = () => {
|
||||
const { t } = useI18n();
|
||||
const toggleSidebar = useUIStore((state) => state.toggleSidebar);
|
||||
const isSidebarOpen = useUIStore((state) => state.isSidebarOpen);
|
||||
const shortcutOverrides = useUIStore((state) => state.shortcutOverrides);
|
||||
const projectActionsContext = useProjectActionsContext();
|
||||
const clusterRef = React.useRef<HTMLDivElement | null>(null);
|
||||
@@ -129,10 +128,6 @@ export const TitlebarLeftControls: React.FC = () => {
|
||||
<ProjectActionsButton
|
||||
projectRef={projectActionsContext.projectRef}
|
||||
directory={projectActionsContext.directory}
|
||||
// While the sidebar is open the controls sit over the frosted
|
||||
// sidebar — let the pill share its translucency instead of painting
|
||||
// an opaque surface (handled under [data-oc-vibrancy] in CSS).
|
||||
className={isSidebarOpen ? 'oc-vibrancy-pill' : undefined}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -288,6 +288,9 @@ const SortableProviderSection: React.FC<{
|
||||
};
|
||||
|
||||
const STICKY_HEADER_OFFSET = 32;
|
||||
const STICKY_FADE_MAX_SIZE = 48;
|
||||
const STICKY_FADE_MIN_SIZE = 32;
|
||||
const STICKY_FADE_CLEAR_MAX_SIZE = 24;
|
||||
|
||||
const scrollIntoView = (container: HTMLElement | null, node: HTMLElement | null) => {
|
||||
if (!node) return;
|
||||
@@ -417,6 +420,9 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
|
||||
const selectionStore = selectionStoreRef.current;
|
||||
const itemRefs = React.useRef<(HTMLDivElement | null)[]>([]);
|
||||
const scrollRef = React.useRef<HTMLElement | null>(null);
|
||||
const sectionHeaderSentinelRefs = React.useRef<Map<string, HTMLDivElement | null>>(new Map());
|
||||
const stickyFadeSizeRef = React.useRef(0);
|
||||
const [stuckSectionHeaders, setStuckSectionHeaders] = React.useState<Set<string>>(new Set());
|
||||
const keyboardOwnsSelectionRef = React.useRef(false);
|
||||
const lastMousePositionRef = React.useRef<{ x: number; y: number } | null>(null);
|
||||
const collapsedRecord = useModelPickerSectionsStore((state) => state.collapsedSections);
|
||||
@@ -495,6 +501,70 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
|
||||
})
|
||||
.filter((provider) => provider.models.length > 0), [allowedProviderSet, isHidden, isModelAllowed, matchesQuery, orderedProviders]);
|
||||
|
||||
const visibleSectionKeys = React.useMemo(() => [
|
||||
...(filteredFavorites.length > 0 ? ['favorites'] : []),
|
||||
...(filteredRecents.length > 0 ? ['recent'] : []),
|
||||
...filteredProviders.map((provider) => `provider:${provider.id}`),
|
||||
], [filteredFavorites.length, filteredProviders, filteredRecents.length]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!stickyHeaders || !scrollRef.current) {
|
||||
setStuckSectionHeaders((previous) => previous.size === 0 ? previous : new Set());
|
||||
return;
|
||||
}
|
||||
|
||||
const root = scrollRef.current;
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
setStuckSectionHeaders((previous) => {
|
||||
const next = new Set(previous);
|
||||
let changed = false;
|
||||
for (const entry of entries) {
|
||||
const sectionKey = (entry.target as HTMLElement).dataset.modelSectionKey;
|
||||
if (!sectionKey) continue;
|
||||
const rootTop = entry.rootBounds?.top ?? root.getBoundingClientRect().top;
|
||||
const isAboveScroller = !entry.isIntersecting && entry.boundingClientRect.top < rootTop;
|
||||
if (next.has(sectionKey) === isAboveScroller) continue;
|
||||
changed = true;
|
||||
if (isAboveScroller) next.add(sectionKey);
|
||||
else next.delete(sectionKey);
|
||||
}
|
||||
return changed ? next : previous;
|
||||
});
|
||||
}, { root, threshold: 0 });
|
||||
|
||||
sectionHeaderSentinelRefs.current.forEach((element) => {
|
||||
if (element) observer.observe(element);
|
||||
});
|
||||
return () => observer.disconnect();
|
||||
}, [stickyHeaders, visibleSectionKeys]);
|
||||
|
||||
const syncStickyFade = React.useCallback((scroller: HTMLElement) => {
|
||||
const hasTopScroll = scroller.scrollTop > 1;
|
||||
const fadeSize = hasTopScroll
|
||||
? Math.min(STICKY_FADE_MIN_SIZE + scroller.scrollTop, STICKY_FADE_MAX_SIZE)
|
||||
: 0;
|
||||
stickyFadeSizeRef.current = fadeSize;
|
||||
scroller.style.setProperty('--scroll-shadow-top-size', `${fadeSize}px`);
|
||||
scroller.style.setProperty(
|
||||
'--scroll-shadow-top-clear-size',
|
||||
`${Math.min(Math.max(fadeSize - 8, 0), STICKY_FADE_CLEAR_MAX_SIZE)}px`,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const blockStickyFadeInteraction = React.useCallback((
|
||||
event: React.MouseEvent<HTMLDivElement> | React.PointerEvent<HTMLDivElement>,
|
||||
) => {
|
||||
if ((event.target as Element).closest('[data-overlay-scrollbar-thumb], [data-model-picker-sticky-header]')) return;
|
||||
const eventY = event.clientY - event.currentTarget.getBoundingClientRect().top;
|
||||
if (eventY >= stickyFadeSizeRef.current) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}, []);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (stickyHeaders && scrollRef.current) syncStickyFade(scrollRef.current);
|
||||
}, [stickyHeaders, syncStickyFade, visibleSectionKeys]);
|
||||
|
||||
const flatModelList = React.useMemo(() => {
|
||||
const items: ModelPickerEntry[] = [];
|
||||
if (!collapsedSections.has('favorites')) filteredFavorites.forEach((entry) => items.push(entry));
|
||||
@@ -571,7 +641,7 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
|
||||
|
||||
const headerClassName = cn(
|
||||
'typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 px-2 py-1.5',
|
||||
stickyHeaders && 'sticky top-0 z-10 [background:linear-gradient(var(--surface-elevated),var(--surface-elevated)),linear-gradient(var(--surface-background),var(--surface-background))]',
|
||||
stickyHeaders && 'sticky top-0 z-20',
|
||||
sectionHeaderClassName,
|
||||
);
|
||||
|
||||
@@ -678,6 +748,15 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
|
||||
const isSectionCollapsed = (key: string) => collapsedSections.has(key);
|
||||
const toggleSectionCollapsed = (key: string) => toggleSection(key);
|
||||
|
||||
const renderSectionSentinel = (key: string) => stickyHeaders ? (
|
||||
<div
|
||||
ref={(element) => { sectionHeaderSentinelRefs.current.set(key, element); }}
|
||||
data-model-section-key={key}
|
||||
className="pointer-events-none absolute top-0 h-px w-full"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : null;
|
||||
|
||||
const renderSectionHeader = (key: string, icon: React.ReactNode, label: React.ReactNode, headerDragProps?: SortableFavoriteHandleProps) => {
|
||||
const collapsed = isSectionCollapsed(key);
|
||||
const toggleKeyDown = (event: React.KeyboardEvent) => {
|
||||
@@ -703,6 +782,7 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
|
||||
tabIndex={0}
|
||||
aria-expanded={!collapsed}
|
||||
title={reorderProviderTitle}
|
||||
data-model-picker-sticky-header={stickyHeaders ? 'true' : undefined}
|
||||
className={cn(headerClassName, 'w-full text-left cursor-grab select-none active:cursor-grabbing')}
|
||||
onClick={() => toggleSectionCollapsed(key)}
|
||||
onKeyDown={toggleKeyDown}
|
||||
@@ -720,6 +800,7 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-model-picker-sticky-header={stickyHeaders ? 'true' : undefined}
|
||||
className={cn(headerClassName, 'w-full text-left cursor-pointer')}
|
||||
onClick={() => toggleSectionCollapsed(key)}
|
||||
aria-expanded={!collapsed}
|
||||
@@ -737,15 +818,43 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
|
||||
provider: (typeof filteredProviders)[number],
|
||||
providerIndex: number,
|
||||
headerDragProps?: SortableFavoriteHandleProps,
|
||||
) => (
|
||||
<>
|
||||
{providerIndex > 0 ? <div className="h-px bg-border/40 my-1" /> : null}
|
||||
{renderSectionHeader(`provider:${provider.id}`, <ProviderLogo providerId={provider.id} className="h-4 w-4 flex-shrink-0" />, provider.name || provider.id, headerDragProps)}
|
||||
{!isSectionCollapsed(`provider:${provider.id}`)
|
||||
? provider.models.map((model) => renderRow({ model, providerID: provider.id, modelID: model.id as string }, 'provider', false, currentFlatIndex++))
|
||||
: null}
|
||||
</>
|
||||
);
|
||||
) => {
|
||||
const sectionKey = `provider:${provider.id}`;
|
||||
return (
|
||||
<>
|
||||
{providerIndex > 0 ? <div className="h-px bg-border/40 my-1" /> : null}
|
||||
<div className="relative">
|
||||
{renderSectionSentinel(sectionKey)}
|
||||
{renderSectionHeader(sectionKey, <ProviderLogo providerId={provider.id} className="h-4 w-4 flex-shrink-0" />, provider.name || provider.id, headerDragProps)}
|
||||
{!isSectionCollapsed(sectionKey)
|
||||
? provider.models.map((model) => renderRow({ model, providerID: provider.id, modelID: model.id as string }, 'provider', false, currentFlatIndex++))
|
||||
: null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
let stuckSectionKey: string | null = null;
|
||||
for (const sectionKey of visibleSectionKeys) {
|
||||
if (stuckSectionHeaders.has(sectionKey)) stuckSectionKey = sectionKey;
|
||||
}
|
||||
// The sidebar can seed its overlay with the first section because its first
|
||||
// header starts flush with the scroller. `Not selected` may precede the
|
||||
// first model section here, so wait for that section's sentinel rather than
|
||||
// showing its identity while the leading action is still visible.
|
||||
const leadingSectionKey = stuckSectionKey ?? (!includeNotSelected ? visibleSectionKeys[0] ?? null : null);
|
||||
const renderSectionIdentity = (sectionKey: string): React.ReactNode => {
|
||||
if (sectionKey === 'favorites') {
|
||||
return <><Icon name="star-fill" className="h-4 w-4 flex-shrink-0 text-primary" /><span className="min-w-0 truncate">{labels.favorites}</span></>;
|
||||
}
|
||||
if (sectionKey === 'recent') {
|
||||
return <><Icon name="time" className="h-4 w-4 flex-shrink-0" /><span className="min-w-0 truncate">{labels.recent}</span></>;
|
||||
}
|
||||
const providerId = sectionKey.startsWith('provider:') ? sectionKey.slice('provider:'.length) : '';
|
||||
const provider = providerById.get(providerId);
|
||||
if (!provider) return null;
|
||||
return <><ProviderLogo providerId={providerId} className="h-4 w-4 flex-shrink-0" /><span className="min-w-0 truncate">{provider.name || provider.id}</span></>;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -764,7 +873,25 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollableOverlay ref={scrollRef} outerClassName={maxHeightClassName} className="overlay-scrollbar-target--no-gutter" style={maxHeightStyle}>
|
||||
<div
|
||||
className="oc-sticky-fade-root relative flex min-h-0 flex-1"
|
||||
onPointerDownCapture={stickyHeaders ? blockStickyFadeInteraction : undefined}
|
||||
onClickCapture={stickyHeaders ? blockStickyFadeInteraction : undefined}
|
||||
onContextMenuCapture={stickyHeaders ? blockStickyFadeInteraction : undefined}
|
||||
>
|
||||
<ScrollableOverlay
|
||||
ref={scrollRef}
|
||||
useScrollShadow={stickyHeaders}
|
||||
hideTopScrollShadow={!stickyHeaders}
|
||||
scrollShadowSize={96}
|
||||
outerClassName={maxHeightClassName}
|
||||
className="oc-sticky-fade-scroller overlay-scrollbar-target--no-gutter"
|
||||
style={{
|
||||
...(stickyHeaders ? { '--scroll-shadow-top-size': '0px' } as React.CSSProperties : {}),
|
||||
...maxHeightStyle,
|
||||
}}
|
||||
onScroll={stickyHeaders ? (event) => syncStickyFade(event.currentTarget) : undefined}
|
||||
>
|
||||
<div className="px-1">
|
||||
{includeNotSelected ? (
|
||||
<>
|
||||
@@ -786,7 +913,8 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
|
||||
) : null}
|
||||
|
||||
{filteredFavorites.length > 0 ? (
|
||||
<div>
|
||||
<div className="relative">
|
||||
{renderSectionSentinel('favorites')}
|
||||
{renderSectionHeader('favorites', <Icon name="star-fill" className="h-4 w-4 text-primary" />, labels.favorites)}
|
||||
{!isSectionCollapsed('favorites') && (favoriteSortingEnabled ? (
|
||||
<DndContext sensors={favoriteRowSensors} collisionDetection={closestCenter} onDragEnd={handleFavoriteDragEnd}>
|
||||
@@ -806,11 +934,14 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
|
||||
) : null}
|
||||
|
||||
{filteredRecents.length > 0 ? (
|
||||
<div>
|
||||
<>
|
||||
{filteredFavorites.length > 0 ? <div className="h-px bg-border/40 my-1" /> : null}
|
||||
{renderSectionHeader('recent', <Icon name="time" className="h-4 w-4" />, labels.recent)}
|
||||
{!isSectionCollapsed('recent') ? filteredRecents.map((entry) => renderRow(entry, 'recent', true, currentFlatIndex++)) : null}
|
||||
</div>
|
||||
<div className="relative">
|
||||
{renderSectionSentinel('recent')}
|
||||
{renderSectionHeader('recent', <Icon name="time" className="h-4 w-4" />, labels.recent)}
|
||||
{!isSectionCollapsed('recent') ? filteredRecents.map((entry) => renderRow(entry, 'recent', true, currentFlatIndex++)) : null}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{(filteredFavorites.length > 0 || filteredRecents.length > 0) && filteredProviders.length > 0 ? <div className="h-px bg-border/40 my-1" /> : null}
|
||||
@@ -834,6 +965,15 @@ export const ModelPickerList: React.FC<ModelPickerListProps> = ({
|
||||
)}
|
||||
</div>
|
||||
</ScrollableOverlay>
|
||||
{stickyHeaders && leadingSectionKey ? (
|
||||
<div
|
||||
className="oc-sticky-fade-overlay pointer-events-none absolute inset-x-0 top-0 z-30 flex items-center gap-2 px-3 py-1.5 typography-micro font-semibold uppercase tracking-wider text-muted-foreground"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{renderSectionIdentity(leadingSectionKey)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="px-3 pt-1 pb-1.5 border-t border-border/40 typography-micro text-muted-foreground">
|
||||
<ModelPickerFooter store={selectionStore} flatModelList={flatModelList} footerContent={footerContent} fallback={labels.keyboardHint} />
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
} from '@/components/ui/select';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
import {
|
||||
invokeDesktop,
|
||||
isDesktopShell,
|
||||
isVSCodeRuntime,
|
||||
isWebRuntime,
|
||||
@@ -410,16 +409,6 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
|
||||
const [themesReloading, setThemesReloading] = React.useState(false);
|
||||
|
||||
// macOS-desktop-only vibrancy toggle. Changing it needs a full relaunch
|
||||
// (vibrancy is a window-creation option), so we persist + restart on save.
|
||||
const macVibrancySupported = React.useMemo(
|
||||
() => isDesktopShell() && typeof window !== 'undefined' && window.__OPENCHAMBER_ELECTRON__?.macVibrancySupported === true,
|
||||
[],
|
||||
);
|
||||
const macVibrancyEnabled = typeof window !== 'undefined' && window.__OPENCHAMBER_ELECTRON__?.macVibrancy === true;
|
||||
const [vibrancyChecked, setVibrancyChecked] = React.useState(macVibrancyEnabled);
|
||||
const [vibrancyRestarting, setVibrancyRestarting] = React.useState(false);
|
||||
|
||||
// macOS-desktop-only dock badge that counts chats with unseen activity.
|
||||
// The tray sync (mac-only) pumps the count to the main process, so the
|
||||
// toggle is offered only where it actually has an effect. No relaunch needed.
|
||||
@@ -979,36 +968,6 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
</div>
|
||||
</SettingsTwoColumn>
|
||||
|
||||
{macVibrancySupported && (
|
||||
<SettingsInset settingsItem="appearance.window-transparency" className="flex flex-col gap-1.5">
|
||||
<SettingsCheckboxRow
|
||||
checked={vibrancyChecked}
|
||||
onChange={setVibrancyChecked}
|
||||
disabled={vibrancyRestarting}
|
||||
label={t('settings.openchamber.visual.field.macVibrancy')}
|
||||
info={t('settings.openchamber.visual.field.macVibrancyHint')}
|
||||
ariaLabel={t('settings.openchamber.visual.field.macVibrancy')}
|
||||
/>
|
||||
{vibrancyChecked !== macVibrancyEnabled && (
|
||||
<div className="pl-6">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={vibrancyRestarting}
|
||||
onClick={() => {
|
||||
setVibrancyRestarting(true);
|
||||
void invokeDesktop('desktop_set_vibrancy', { enabled: vibrancyChecked });
|
||||
}}
|
||||
>
|
||||
{vibrancyRestarting
|
||||
? t('settings.openchamber.visual.actions.restarting')
|
||||
: t('settings.openchamber.visual.actions.saveAndRestart')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</SettingsInset>
|
||||
)}
|
||||
|
||||
{dockBadgeSupported && (
|
||||
<SettingsInset settingsItem="appearance.dock-badge">
|
||||
<SettingsCheckboxRow
|
||||
|
||||
@@ -1143,7 +1143,6 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
||||
data-slot="dropdown-menu-content"
|
||||
finalFocus={() => (renamingFolderId || editingIdRef.current) ? false : true}
|
||||
style={{
|
||||
backgroundColor: 'var(--surface-elevated)',
|
||||
color: 'var(--surface-elevated-foreground)',
|
||||
}}
|
||||
className={cn(dropdownMenuPopupClass, 'min-w-[180px]')}
|
||||
|
||||
@@ -218,7 +218,7 @@ function SidebarProjectsListComponent(props: Props): React.ReactNode {
|
||||
hideTopScrollShadow={!enableStickyFade}
|
||||
scrollShadowSize={96}
|
||||
outerClassName="flex-1 min-h-0"
|
||||
className={cn('oc-sidebar-scroller space-y-1.5 pb-1 pl-2.5 pr-2 [overflow-anchor:none]', props.mobileVariant ? '' : '')}
|
||||
className={cn('oc-sidebar-scroller oc-sticky-fade-scroller space-y-1.5 pb-1 pl-2.5 pr-2 [overflow-anchor:none]', props.mobileVariant ? '' : '')}
|
||||
style={enableStickyFade ? { '--scroll-shadow-top-size': '0px' } as React.CSSProperties : undefined}
|
||||
onScroll={enableStickyFade ? (event) => syncTopFade(event.currentTarget) : undefined}
|
||||
>
|
||||
|
||||
@@ -29,7 +29,6 @@ function ContextMenuContent({ className, positionerClassName, children, style, .
|
||||
<BaseContextMenu.Popup
|
||||
data-slot="dropdown-menu-content"
|
||||
style={{
|
||||
backgroundColor: "var(--surface-elevated)",
|
||||
color: "var(--surface-elevated-foreground)",
|
||||
...style,
|
||||
}}
|
||||
|
||||
@@ -61,7 +61,7 @@ const DialogOverlay = React.forwardRef<
|
||||
ref={ref as React.Ref<HTMLDivElement>}
|
||||
data-slot="dialog-overlay"
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/50 dark:bg-black/75",
|
||||
"oc-glass-backdrop fixed inset-0 z-50 bg-black/25 dark:bg-black/40",
|
||||
"transition-opacity duration-150 ease-out",
|
||||
"data-[starting-style]:opacity-0 data-[ending-style]:opacity-0",
|
||||
className
|
||||
@@ -93,7 +93,7 @@ function DialogContent({
|
||||
data-slot="dialog-content"
|
||||
data-state-slot="dialog"
|
||||
className={cn(
|
||||
"relative pointer-events-auto bg-background text-foreground flex flex-col w-full max-w-lg max-h-full gap-4 rounded-xl border p-6 shadow-none overflow-y-auto pwa-dialog-content origin-center",
|
||||
"oc-glass-dialog relative pointer-events-auto text-foreground flex flex-col w-full max-w-lg max-h-full gap-4 rounded-xl border p-6 shadow-none overflow-y-auto pwa-dialog-content origin-center",
|
||||
"transition-all duration-150 ease-out",
|
||||
"data-[starting-style]:opacity-0 data-[starting-style]:scale-[0.98]",
|
||||
"data-[ending-style]:opacity-0 data-[ending-style]:scale-[0.98]",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const dropdownMenuPopupClass = "app-region-no-drag transition-all duration-150 ease-out data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 z-50 max-h-[var(--available-height)] min-w-[8rem] origin-[var(--transform-origin)] overflow-visible rounded-xl p-1 shadow-[inset_0_1px_0_0_rgba(255,255,255,0.8),inset_0_0_0_1px_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.10),0_1px_2px_-0.5px_rgba(0,0,0,0.08),0_4px_8px_-2px_rgba(0,0,0,0.08),0_12px_20px_-4px_rgba(0,0,0,0.08)] dark:shadow-[inset_0_1px_0_0_rgba(255,255,255,0.12),inset_0_0_0_1px_rgba(255,255,255,0.08),0_0_0_1px_rgba(0,0,0,0.36),0_1px_1px_-0.5px_rgba(0,0,0,0.22),0_3px_3px_-1.5px_rgba(0,0,0,0.20),0_6px_6px_-3px_rgba(0,0,0,0.16)]";
|
||||
export const dropdownMenuPopupClass = "oc-glass-popover oc-glass-floating app-region-no-drag transition-all duration-150 ease-out data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 z-50 max-h-[var(--available-height)] min-w-[8rem] origin-[var(--transform-origin)] overflow-visible rounded-xl p-1";
|
||||
export const dropdownMenuItemClass = "data-[highlighted]:bg-interactive-hover hover:bg-interactive-hover data-[variant=destructive]:text-destructive data-[variant=destructive]:hover:bg-destructive/10 dark:data-[variant=destructive]:hover:bg-destructive/20 data-[variant=destructive]:hover:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-pointer items-center gap-2 rounded-lg px-2 py-1 typography-ui-label outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5";
|
||||
export const dropdownMenuSubTriggerClass = "data-[highlighted]:bg-interactive-hover hover:bg-interactive-hover [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-pointer items-center gap-2 rounded-lg px-2 py-1 typography-ui-label outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5";
|
||||
export const dropdownMenuSeparatorClass = "bg-border -mx-1 my-0.5 h-px";
|
||||
|
||||
@@ -123,7 +123,6 @@ function DropdownMenuContent({
|
||||
<BaseMenu.Popup
|
||||
data-slot="dropdown-menu-content"
|
||||
style={{
|
||||
backgroundColor: 'var(--surface-elevated)',
|
||||
color: 'var(--surface-elevated-foreground)',
|
||||
...style,
|
||||
}}
|
||||
@@ -280,7 +279,6 @@ function DropdownMenuSubContent({
|
||||
<BaseMenu.Popup
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
style={{
|
||||
backgroundColor: 'var(--surface-elevated)',
|
||||
color: 'var(--surface-elevated-foreground)',
|
||||
}}
|
||||
className={cn(
|
||||
|
||||
@@ -192,11 +192,10 @@ function SelectContent({
|
||||
<BaseSelect.Popup
|
||||
data-slot="select-content"
|
||||
style={{
|
||||
backgroundColor: 'var(--surface-elevated)',
|
||||
color: 'var(--surface-elevated-foreground)',
|
||||
}}
|
||||
className={cn(
|
||||
"pointer-events-auto transition-all duration-150 ease-out data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 relative z-[120] max-h-[var(--available-height)] min-w-[8rem] origin-[var(--transform-origin)] overflow-x-hidden rounded-xl shadow-[inset_0_1px_0_0_rgba(255,255,255,0.8),inset_0_0_0_1px_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.10),0_1px_2px_-0.5px_rgba(0,0,0,0.08),0_4px_8px_-2px_rgba(0,0,0,0.08),0_12px_20px_-4px_rgba(0,0,0,0.08)] dark:shadow-[inset_0_1px_0_0_rgba(255,255,255,0.12),inset_0_0_0_1px_rgba(255,255,255,0.08),0_0_0_1px_rgba(0,0,0,0.36),0_1px_1px_-0.5px_rgba(0,0,0,0.22),0_3px_3px_-1.5px_rgba(0,0,0,0.20),0_6px_6px_-3px_rgba(0,0,0,0.16)]",
|
||||
"oc-glass-popover oc-glass-floating pointer-events-auto transition-all duration-150 ease-out data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 relative z-[120] max-h-[var(--available-height)] min-w-[8rem] origin-[var(--transform-origin)] overflow-x-hidden rounded-xl",
|
||||
!alignItemWithTrigger &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
fitContent && "w-max min-w-0",
|
||||
|
||||
@@ -277,7 +277,7 @@ function TooltipContent({
|
||||
// data-instant is set when moving between grouped tooltips
|
||||
// (shared TooltipProvider): reposition without replaying the
|
||||
// full exit/enter animation.
|
||||
"bg-[var(--surface-elevated)] text-[var(--surface-elevated-foreground)] border border-border/60 transition-all duration-150 ease-out data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 data-[instant]:transition-none data-[instant]:duration-0 z-50 w-fit origin-[var(--transform-origin)] rounded-xl px-3 py-1.5 typography-meta text-balance overflow-hidden",
|
||||
"oc-glass-tooltip text-[var(--surface-elevated-foreground)] border border-border/60 transition-all duration-150 ease-out data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 data-[instant]:transition-none data-[instant]:duration-0 z-50 w-fit origin-[var(--transform-origin)] rounded-xl px-3 py-1.5 typography-meta text-balance overflow-hidden",
|
||||
className
|
||||
)}
|
||||
style={{ ...style }}
|
||||
|
||||
@@ -38,7 +38,7 @@ export const SettingsWindow: React.FC<SettingsWindowProps> = ({ open, onOpenChan
|
||||
<Dialog.Portal>
|
||||
<Dialog.Backdrop
|
||||
className={cn(
|
||||
'fixed inset-0 z-50 bg-black/50 dark:bg-black/75',
|
||||
'oc-glass-backdrop fixed inset-0 z-50 bg-black/25 dark:bg-black/40',
|
||||
'transition-opacity duration-150 ease-out',
|
||||
'data-[starting-style]:opacity-0 data-[ending-style]:opacity-0',
|
||||
)}
|
||||
@@ -50,7 +50,7 @@ export const SettingsWindow: React.FC<SettingsWindowProps> = ({ open, onOpenChan
|
||||
'relative pointer-events-auto',
|
||||
'w-[90vw] max-w-[1200px] h-[85vh] max-h-[900px]',
|
||||
'rounded-xl border shadow-none overflow-hidden origin-center',
|
||||
'bg-background',
|
||||
'oc-glass-dialog',
|
||||
'transition-all duration-150 ease-out',
|
||||
'data-[starting-style]:opacity-0 data-[starting-style]:scale-[0.98]',
|
||||
'data-[ending-style]:opacity-0 data-[ending-style]:scale-[0.98]',
|
||||
|
||||
Reference in New Issue
Block a user