Merge pull request #1199 from tidalcycles/origin/daslyfe/panel_fixes

Make panel hover behavior optional
This commit is contained in:
Jade (Rose) Rowland 2024-10-21 11:22:52 -07:00 committed by GitHub
commit 1d05051fd6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 134 additions and 47 deletions

View file

@ -13,7 +13,7 @@ export default function ReplEditor(Props) {
const { context } = Props; const { context } = Props;
const { containerRef, editorRef, error, init, pending } = context; const { containerRef, editorRef, error, init, pending } = context;
const settings = useSettings(); const settings = useSettings();
const { panelPosition } = settings; const { panelPosition, isZen } = settings;
return ( return (
<div className="h-full flex flex-col relative"> <div className="h-full flex flex-col relative">
@ -21,10 +21,10 @@ export default function ReplEditor(Props) {
<Header context={context} /> <Header context={context} />
<div className="grow flex relative overflow-hidden"> <div className="grow flex relative overflow-hidden">
<Code containerRef={containerRef} editorRef={editorRef} init={init} /> <Code containerRef={containerRef} editorRef={editorRef} init={init} />
{panelPosition === 'right' && <VerticalPanel context={context} />} {!isZen && panelPosition === 'right' && <VerticalPanel context={context} />}
</div> </div>
<UserFacingErrorMessage error={error} /> <UserFacingErrorMessage error={error} />
{panelPosition === 'bottom' && <HorizontalPanel context={context} />} {!isZen && panelPosition === 'bottom' && <HorizontalPanel context={context} />}
</div> </div>
); );
} }

View file

@ -6,6 +6,7 @@ export function ButtonGroup({ value, onChange, items }) {
{Object.entries(items).map(([key, label], i, arr) => ( {Object.entries(items).map(([key, label], i, arr) => (
<button <button
key={key} key={key}
id={key}
onClick={() => onChange(key)} onClick={() => onChange(key)}
className={cx( className={cx(
'px-2 border-b h-8 whitespace-nowrap', 'px-2 border-b h-8 whitespace-nowrap',

View file

@ -3,7 +3,7 @@ import useEvent from '@src/useEvent.mjs';
import cx from '@src/cx.mjs'; import cx from '@src/cx.mjs';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { setPanelPinned, setActiveFooter as setTab, useSettings } from '../../../settings.mjs'; import { setPanelPinned, setActiveFooter as setTab, setIsPanelOpened, useSettings } from '../../../settings.mjs';
import { ConsoleTab } from './ConsoleTab'; import { ConsoleTab } from './ConsoleTab';
import { FilesTab } from './FilesTab'; import { FilesTab } from './FilesTab';
import { Reference } from './Reference'; import { Reference } from './Reference';
@ -11,28 +11,31 @@ import { SettingsTab } from './SettingsTab';
import { SoundsTab } from './SoundsTab'; import { SoundsTab } from './SoundsTab';
import { WelcomeTab } from './WelcomeTab'; import { WelcomeTab } from './WelcomeTab';
import { PatternsTab } from './PatternsTab'; import { PatternsTab } from './PatternsTab';
import { ChevronLeftIcon } from '@heroicons/react/16/solid'; import { ChevronLeftIcon, XMarkIcon } from '@heroicons/react/16/solid';
const TAURI = typeof window !== 'undefined' && window.__TAURI__; const TAURI = typeof window !== 'undefined' && window.__TAURI__;
export function HorizontalPanel({ context }) { export function HorizontalPanel({ context }) {
const settings = useSettings(); const settings = useSettings();
const { isPanelPinned: pinned, activeFooter: tab } = settings; const { isPanelOpen, activeFooter: tab } = settings;
return ( return (
<PanelNav <PanelNav
className={cx( settings={settings}
'hover:max-h-[360px] hover:min-h-[360px] justify-between flex flex-col', className={cx(isPanelOpen ? `min-h-[360px] max-h-[360px]` : 'min-h-12 max-h-12', 'overflow-hidden flex flex-col')}
pinned ? `min-h-[360px] max-h-[360px]` : 'min-h-10 max-h-10',
)}
> >
<div className="flex h-full overflow-auto "> {isPanelOpen && (
<PanelContent context={context} tab={tab} /> <div className="flex h-full overflow-auto pr-10 ">
<PanelContent context={context} tab={tab} />
</div>
)}
<div className="absolute right-4 pt-4">
<PanelActionButton settings={settings} />
</div> </div>
<div className="flex justify-between min-h-10 max-h-10 pr-2 items-center"> <div className="flex justify-between min-h-12 max-h-12 grid-cols-2 items-center">
<Tabs setTab={setTab} tab={tab} pinned={pinned} /> <Tabs setTab={setTab} tab={tab} />
<PinButton pinned={pinned} setPinned={setPanelPinned} />
</div> </div>
</PanelNav> </PanelNav>
); );
@ -40,28 +43,37 @@ export function HorizontalPanel({ context }) {
export function VerticalPanel({ context }) { export function VerticalPanel({ context }) {
const settings = useSettings(); const settings = useSettings();
const { isPanelPinned: pinned, activeFooter: tab } = settings; const { activeFooter: tab, isPanelOpen } = settings;
return ( return (
<PanelNav <PanelNav
className={cx( settings={settings}
'hover:min-w-[min(600px,80vw)] hover:max-w-[min(600px,80vw)]', className={cx(isPanelOpen ? `min-w-[min(600px,80vw)] max-w-[min(600px,80vw)]` : 'min-w-12 max-w-12')}
pinned ? `min-w-[min(600px,80vw)] max-w-[min(600px,80vw)]` : 'min-w-8',
)}
> >
<div className={cx('group-hover:flex flex-col h-full', pinned ? 'flex' : 'hidden')}> {isPanelOpen ? (
<div className="flex justify-between w-full "> <div className={cx('flex flex-col h-full')}>
<Tabs setTab={setTab} tab={tab} pinned={pinned} /> <div className="flex justify-between w-full ">
<PinButton pinned={pinned} setPinned={setPanelPinned} /> <Tabs setTab={setTab} tab={tab} />
</div> <PanelActionButton settings={settings} />
</div>
<div className="overflow-auto h-full"> <div className="overflow-auto h-full">
<PanelContent context={context} tab={tab} /> <PanelContent context={context} tab={tab} />
</div>
</div> </div>
</div> ) : (
<div className={cx(pinned ? 'hidden' : 'flex flex-col items-center justify-center h-full group-hover:hidden ')}> <button
<ChevronLeftIcon className="text-foreground opacity-50 w-6 h-6" /> onClick={(e) => {
</div> setIsPanelOpened(true);
}}
aria-label="open menu panel"
className={cx(
'flex flex-col hover:bg-lineBackground items-center cursor-pointer justify-center w-full h-full',
)}
>
<ChevronLeftIcon className="text-foreground opacity-50 w-6 h-6" />
</button>
)}
</PanelNav> </PanelNav>
); );
} }
@ -78,11 +90,27 @@ if (TAURI) {
tabNames.files = 'files'; tabNames.files = 'files';
} }
function PanelNav({ children, className, ...props }) { function PanelNav({ children, className, settings, ...props }) {
const isHoverBehavior = settings.togglePanelTrigger === 'hover';
return ( return (
<nav <nav
aria-label="Settings Menu" onClick={() => {
className={cx('bg-lineHighlight group transition-all overflow-x-auto', className)} if (!settings.isPanelOpen) {
setIsPanelOpened(true);
}
}}
onMouseEnter={() => {
if (isHoverBehavior && !settings.isPanelOpen) {
setIsPanelOpened(true);
}
}}
onMouseLeave={() => {
if (isHoverBehavior && !settings.isPanelPinned) {
setIsPanelOpened(false);
}
}}
aria-label="Menu Panel"
className={cx('bg-lineHighlight group overflow-x-auto', className)}
{...props} {...props}
> >
{children} {children}
@ -134,7 +162,7 @@ function PanelContent({ context, tab }) {
function PanelTab({ label, isSelected, onClick }) { function PanelTab({ label, isSelected, onClick }) {
return ( return (
<> <>
<div <button
onClick={onClick} onClick={onClick}
className={cx( className={cx(
'h-8 px-2 text-foreground cursor-pointer hover:opacity-50 flex items-center space-x-1 border-b', 'h-8 px-2 text-foreground cursor-pointer hover:opacity-50 flex items-center space-x-1 border-b',
@ -142,13 +170,13 @@ function PanelTab({ label, isSelected, onClick }) {
)} )}
> >
{label} {label}
</div> </button>
</> </>
); );
} }
function Tabs({ setTab, tab }) { function Tabs({ setTab, tab, className }) {
return ( return (
<div className={cx('flex select-none max-w-full overflow-auto pb-2')}> <div className={cx('flex select-none max-w-full overflow-auto pb-2', className)}>
{Object.keys(tabNames).map((key) => { {Object.keys(tabNames).map((key) => {
const val = tabNames[key]; const val = tabNames[key];
return <PanelTab key={key} isSelected={tab === val} label={key} onClick={() => setTab(val)} />; return <PanelTab key={key} isSelected={tab === val} label={key} onClick={() => setTab(val)} />;
@ -157,15 +185,28 @@ function Tabs({ setTab, tab }) {
); );
} }
function PinButton({ pinned, setPinned }) { function PanelActionButton({ settings }) {
const { togglePanelTrigger, isPanelPinned, isPanelOpen } = settings;
const isHoverBehavior = togglePanelTrigger === 'hover';
if (!isPanelOpen) {
return;
}
if (isHoverBehavior) {
return <PinButton pinned={isPanelPinned} />;
}
return <CloseButton onClick={() => setIsPanelOpened(false)} />;
}
function PinButton({ pinned }) {
return ( return (
<button <button
onClick={() => setPinned(!pinned)} onClick={() => setPanelPinned(!pinned)}
className={cx( className={cx(
'text-foreground max-h-8 min-h-8 max-w-8 min-w-8 items-center justify-center p-1.5 group-hover:flex', 'text-foreground max-h-8 min-h-8 max-w-8 min-w-8 items-center justify-center p-1.5 group-hover:flex',
pinned ? 'flex' : 'hidden', pinned ? 'flex' : 'hidden',
)} )}
aria-label="Pin Settings Menu" aria-label="Pin Menu Panel"
> >
<svg <svg
stroke="currentColor" stroke="currentColor"
@ -182,6 +223,20 @@ function PinButton({ pinned, setPinned }) {
); );
} }
function CloseButton({ onClick }) {
return (
<button
onClick={onClick}
className={cx(
'text-foreground max-h-8 min-h-8 max-w-8 min-w-8 items-center justify-center p-1.5 group-hover:flex',
)}
aria-label="Close Menu"
>
<XMarkIcon />
</button>
);
}
function useLogger(onTrigger) { function useLogger(onTrigger) {
useEvent(logger.key, onTrigger); useEvent(logger.key, onTrigger);
} }

View file

@ -101,6 +101,7 @@ export function SettingsTab({ started }) {
panelPosition, panelPosition,
audioDeviceName, audioDeviceName,
audioEngineTarget, audioEngineTarget,
togglePanelTrigger,
} = useSettings(); } = useSettings();
const shouldAlwaysSync = isUdels(); const shouldAlwaysSync = isUdels();
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null; const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
@ -170,6 +171,31 @@ export function SettingsTab({ started }) {
items={{ bottom: 'Bottom', right: 'Right' }} items={{ bottom: 'Bottom', right: 'Right' }}
></ButtonGroup> ></ButtonGroup>
</FormItem> </FormItem>
<FormItem label="Open Panel on: ">
<ButtonGroup
value={togglePanelTrigger}
onChange={(value) => settingsMap.setKey('togglePanelTrigger', value)}
items={{ click: 'Click', hover: 'Hover' }}
></ButtonGroup>
{/* <Checkbox
label="Click"
onChange={(cbEvent) => {
if (cbEvent.target.checked) {
settingsMap.setKey('togglePanelTrigger', 'click');
}
}}
value={togglePanelTrigger != 'hover'}
/>
<Checkbox
label="Hover"
onChange={(cbEvent) => {
if (cbEvent.target.checked) {
settingsMap.setKey('togglePanelTrigger', 'hover');
}
}}
value={togglePanelTrigger == 'hover'}
/> */}
</FormItem>
<FormItem label="Code Settings"> <FormItem label="Code Settings">
<Checkbox <Checkbox
label="Enable bracket matching" label="Enable bracket matching"

View file

@ -52,9 +52,9 @@ export function SoundsTab() {
}); });
return ( return (
<div id="sounds-tab" className="px-4 flex flex-col w-full h-full dark:text-white text-stone-900"> <div id="sounds-tab" className="px-4 flex flex-col w-full h-full dark:text-white text-stone-900">
<input <input
className="w-full p-1 bg-background rounded-md pb-2" className="w-full p-1 bg-background rounded-md my-2"
placeholder="Search" placeholder="Search"
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}

View file

@ -30,8 +30,11 @@ export const defaultSettings = {
isZen: false, isZen: false,
soundsFilter: 'all', soundsFilter: 'all',
patternFilter: 'community', patternFilter: 'community',
panelPosition: window.innerWidth > 1000 ? 'right' : 'bottom', // panelPosition: window.innerWidth > 1000 ? 'right' : 'bottom', //FIX: does not work on astro
isPanelPinned: true, panelPosition: 'right',
isPanelPinned: false,
isPanelOpen: true,
togglePanelTrigger: 'click', //click | hover
userPatterns: '{}', userPatterns: '{}',
audioDeviceName: defaultAudioDeviceName, audioDeviceName: defaultAudioDeviceName,
audioEngineTarget: audioEngineTargets.webaudio, audioEngineTarget: audioEngineTargets.webaudio,
@ -61,7 +64,6 @@ export function useSettings() {
return { return {
...state, ...state,
isZen: parseBoolean(state.isZen), isZen: parseBoolean(state.isZen),
isPanelPinned: parseBoolean(state.isPanelPinned),
isBracketMatchingEnabled: parseBoolean(state.isBracketMatchingEnabled), isBracketMatchingEnabled: parseBoolean(state.isBracketMatchingEnabled),
isBracketClosingEnabled: parseBoolean(state.isBracketClosingEnabled), isBracketClosingEnabled: parseBoolean(state.isBracketClosingEnabled),
isLineNumbersDisplayed: parseBoolean(state.isLineNumbersDisplayed), isLineNumbersDisplayed: parseBoolean(state.isLineNumbersDisplayed),
@ -74,12 +76,15 @@ export function useSettings() {
isSyncEnabled: isUdels() ? true : parseBoolean(state.isSyncEnabled), isSyncEnabled: isUdels() ? true : parseBoolean(state.isSyncEnabled),
fontSize: Number(state.fontSize), fontSize: Number(state.fontSize),
panelPosition: state.activeFooter !== '' && !isUdels() ? state.panelPosition : 'bottom', // <-- keep this 'bottom' where it is! panelPosition: state.activeFooter !== '' && !isUdels() ? state.panelPosition : 'bottom', // <-- keep this 'bottom' where it is!
isPanelPinned: parseBoolean(state.isPanelPinned),
isPanelOpen: parseBoolean(state.isPanelOpen),
userPatterns: userPatterns, userPatterns: userPatterns,
}; };
} }
export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab); export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
export const setPanelPinned = (isPinned) => settingsMap.setKey('isPanelPinned', isPinned); export const setPanelPinned = (bool) => settingsMap.setKey('isPanelPinned', bool);
export const setIsPanelOpened = (bool) => settingsMap.setKey('isPanelOpen', bool);
export const setIsZen = (active) => settingsMap.setKey('isZen', !!active); export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);