feat: mobile responsiveness

This commit is contained in:
Jade (Rose) Rowland 2024-10-14 12:45:55 -04:00
parent 96802a271c
commit b0d2115f43
9 changed files with 78 additions and 59 deletions

View file

@ -2,7 +2,7 @@ import cx from '@src/cx.mjs';
export function ConsoleTab({ log }) { export function ConsoleTab({ log }) {
return ( return (
<div id="console-tab" className="break-all px-4 dark:text-white text-stone-900 text-sm py-4"> <div id="console-tab" className="break-all px-4 dark:text-white text-stone-900 text-sm py-2">
<pre aria-hidden="true">{`███████╗████████╗██████╗ ██╗ ██╗██████╗ ███████╗██╗ <pre aria-hidden="true">{`███████╗████████╗██████╗ ██╗ ██╗██████╗ ███████╗██╗

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 { setActiveFooter as setTab, useSettings } from '../../../settings.mjs'; import { setPanelPinned, setActiveFooter as setTab, 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';
@ -16,50 +16,56 @@ import { ChevronLeftIcon } 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 [pinned, setPinned] = useState(true);
const settings = useSettings(); const settings = useSettings();
const tab = settings.activeFooter; const { isPanelPinned: pinned, activeFooter: tab } = settings;
const [isHovered, setIsHovered] = useState(false);
const showContent = isHovered || pinned;
return ( return (
<PanelNav <PanelNav
onMouseEnter={(x) => setIsHovered(true)}
onMouseLeave={(x) => setIsHovered(false)}
className={cx( className={cx(
'hover:max-h-[360px] hover:min-h-[360px] flex flex-col overflow-hidden ', 'hover:max-h-[360px] hover:min-h-[360px] justify-between flex flex-col',
pinned ? `min-h-[360px] max-h-[360px]` : 'min-h-8 max-h-8', pinned ? `min-h-[360px] max-h-[360px]` : 'min-h-10 max-h-10',
)} )}
> >
{showContent && (
<div className={cx('group-hover:flex flex-col h-full overflow-auto', pinned ? 'flex' : 'hidden')}> <div className="flex h-full overflow-auto">
<PanelContent context={context} tab={tab} /> <PanelContent context={context} tab={tab} />
</div> </div>
<div className="flex justify-between min-h-8 max-h-8"> )}
<div className="flex justify-between min-h-10 max-h-10 pr-2 items-center">
<Tabs setTab={setTab} tab={tab} pinned={pinned} /> <Tabs setTab={setTab} tab={tab} pinned={pinned} />
<PinButton pinned={pinned} setPinned={setPinned} /> <PinButton pinned={pinned} setPinned={setPanelPinned} />
</div> </div>
</PanelNav> </PanelNav>
); );
} }
export function VerticalPanel({ context }) { export function VerticalPanel({ context }) {
const [pinned, setPinned] = useState(true);
const settings = useSettings(); const settings = useSettings();
const tab = settings.activeFooter; const { isPanelPinned: pinned, activeFooter: tab } = settings;
const [isHovered, setIsHovered] = useState(false);
const showContent = isHovered || pinned;
return ( return (
<PanelNav <PanelNav
className={cx( onMouseEnter={(x) => setIsHovered(true)}
'hover:min-w-[600px] hover:max-w-[600px]', onMouseLeave={(x) => setIsHovered(false)}
pinned ? `min-w-[600px] max-w-[600px]` : ' min-w-8 max-w-8', className={cx('hover:w-[600px]', pinned ? `w-[600px]` : 'w-8')}
)}
> >
<div className={cx('group-hover:flex flex-col h-full', pinned ? 'flex' : 'hidden')}> <div className={cx('group-hover:flex flex-col h-full', pinned ? 'flex' : 'hidden')}>
<div className="flex justify-between"> <div className="flex justify-between w-full ">
<Tabs setTab={setTab} tab={tab} pinned={pinned} /> <Tabs setTab={setTab} tab={tab} pinned={pinned} />
<PinButton pinned={pinned} setPinned={setPinned} /> <PinButton pinned={pinned} setPinned={setPanelPinned} />
</div>
<div className="overflow-auto">
<PanelContent context={context} tab={tab} />
</div> </div>
{showContent && (
<div className="overflow-auto">
<PanelContent context={context} tab={tab} />
</div>
)}
</div> </div>
<div className={cx(pinned ? 'hidden' : 'flex flex-col items-center justify-center h-full group-hover:hidden ')}> <div className={cx(pinned ? 'hidden' : 'flex flex-col items-center justify-center h-full group-hover:hidden ')}>
<ChevronLeftIcon className="text-foreground opacity-50 w-6 h-6" /> <ChevronLeftIcon className="text-foreground opacity-50 w-6 h-6" />
</div> </div>
@ -71,17 +77,21 @@ const tabNames = {
welcome: 'intro', welcome: 'intro',
patterns: 'patterns', patterns: 'patterns',
sounds: 'sounds', sounds: 'sounds',
console: 'console',
reference: 'reference', reference: 'reference',
console: 'console',
settings: 'settings', settings: 'settings',
}; };
if (TAURI) { if (TAURI) {
tabNames.files = 'files'; tabNames.files = 'files';
} }
function PanelNav({ children, className }) { function PanelNav({ children, className, ...props }) {
return ( return (
<nav aria-label="Settings Menu" className={cx('bg-lineHighlight group transition-all', className)}> <nav
aria-label="Settings Menu"
className={cx('bg-lineHighlight group transition-all overflow-x-auto', className)}
{...props}
>
{children} {children}
</nav> </nav>
); );
@ -159,7 +169,7 @@ function PinButton({ pinned, setPinned }) {
<button <button
onClick={() => setPinned(!pinned)} onClick={() => setPinned(!pinned)}
className={cx( className={cx(
'text-foreground w-8 h-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 Settings Menu"
@ -168,6 +178,7 @@ function PinButton({ pinned, setPinned }) {
stroke="currentColor" stroke="currentColor"
fill={'currentColor'} fill={'currentColor'}
strokeWidth="0" strokeWidth="0"
className="w-full h-full"
opacity={pinned ? 1 : '.3'} opacity={pinned ? 1 : '.3'}
viewBox="0 0 16 16" viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View file

@ -77,7 +77,7 @@ function PatternButtons({ patterns, activePattern, onClick, started }) {
function ActionButton({ children, onClick, label, labelIsHidden }) { function ActionButton({ children, onClick, label, labelIsHidden }) {
return ( return (
<button className="hover:opacity-50" onClick={onClick} title={label}> <button className="hover:opacity-50 text-nowrap" onClick={onClick} title={label}>
{labelIsHidden !== true && label} {labelIsHidden !== true && label}
{children} {children}
</button> </button>
@ -155,7 +155,7 @@ export function PatternsTab({ context }) {
</div> </div>
)} )}
<section className="flex overflow-y-scroll max-h-full flex-col pb-4"> <section className="flex overflow-y-scroll max-h-full flex-col">
{patternFilter === patternFilterName.user && ( {patternFilter === patternFilterName.user && (
<PatternButtons <PatternButtons
onClick={(id) => onClick={(id) =>

View file

@ -25,32 +25,34 @@ export function Reference() {
}, [search]); }, [search]);
return ( return (
<div className="flex h-full w-full pt-2 text-foreground overflow-hidden"> <div className="flex h-full w-full p-2 text-foreground overflow-hidden">
<div className="w-42 flex-none h-full overflow-y-auto overflow-x-hidden pr-4"> <div className="h-full flex flex-col gap-2 w-1/3 max-w-72 ">
<div class="w-full ml-2 mb-2 top-0 sticky"> <div class="w-full flex">
<input <input
className="w-full p-1 bg-background rounded-md" className="w-full p-1 bg-background rounded-md border-none"
placeholder="Search" placeholder="Search"
value={search} value={search}
onInput={(event) => setSearch(event.target.value)} onInput={(event) => setSearch(event.target.value)}
/> />
</div> </div>
{visibleFunctions.map((entry, i) => ( <div className="flex flex-col h-full overflow-y-auto gap-1.5 bg-background bg-opacity-50 rounded-md">
<a {visibleFunctions.map((entry, i) => (
key={i} <a
className="cursor-pointer block hover:bg-lineHighlight py-1 px-4" key={i}
onClick={() => { className="cursor-pointer flex-none hover:bg-lineHighlight overflow-x-hidden px-1 text-ellipsis"
const el = document.getElementById(`doc-${i}`); onClick={() => {
const container = document.getElementById('reference-container'); const el = document.getElementById(`doc-${i}`);
container.scrollTo(0, el.offsetTop); const container = document.getElementById('reference-container');
}} container.scrollTo(0, el.offsetTop);
> }}
{entry.name} {/* <span className="text-gray-600">{entry.meta.filename}</span> */} >
</a> {entry.name} {/* <span className="text-gray-600">{entry.meta.filename}</span> */}
))} </a>
))}
</div>
</div> </div>
<div className="break-normal w-full h-full overflow-auto pl-4 flex relative" id="reference-container"> <div className="break-normal flex-grow flex-col overflow-y-auto overflow-x-hidden px-2 flex relative" id="reference-container">
<div className="prose dark:prose-invert max-w-full pr-4"> <div className='prose dark:prose-invert min-w-full px-1 '>
<h2>API Reference</h2> <h2>API Reference</h2>
<p> <p>
This is the long list functions you can use! Remember that you don't need to remember all of those and that This is the long list functions you can use! Remember that you don't need to remember all of those and that
@ -78,7 +80,7 @@ export function Reference() {
))} ))}
</section> </section>
))} ))}
</div> </div>
</div> </div>
</div> </div>
); );

View file

@ -105,7 +105,7 @@ export function SettingsTab({ started }) {
const shouldAlwaysSync = isUdels(); const shouldAlwaysSync = isUdels();
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null; const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
return ( return (
<div className="text-foreground p-4 space-y-4"> <div className="text-foreground p-4 space-y-4 w-full">
{canChangeAudioDevice && ( {canChangeAudioDevice && (
<FormItem label="Audio Output Device"> <FormItem label="Audio Output Device">
<AudioDeviceSelector <AudioDeviceSelector

View file

@ -1,7 +1,7 @@
import useEvent from '@src/useEvent.mjs'; import useEvent from '@src/useEvent.mjs';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { getAudioContext, soundMap, connectToDestination } from '@strudel/webaudio'; import { getAudioContext, soundMap, connectToDestination } from '@strudel/webaudio';
import React, { useMemo, useRef, useState } from 'react'; import { useMemo, useRef, useState } from 'react';
import { settingsMap, useSettings } from '../../../settings.mjs'; import { settingsMap, useSettings } from '../../../settings.mjs';
import { ButtonGroup } from './Forms.jsx'; import { ButtonGroup } from './Forms.jsx';
import ImportSoundsButton from './ImportSoundsButton.jsx'; import ImportSoundsButton from './ImportSoundsButton.jsx';
@ -53,14 +53,14 @@ 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">
<div className="w-full ml-2 mb-2 top-0 sticky">
<input <input
className="w-full p-1 bg-background rounded-md" className="w-full p-1 bg-background rounded-md pb-2"
placeholder="Search" placeholder="Search"
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
/> />
</div>
<div className="pb-2 flex shrink-0 flex-wrap"> <div className="pb-2 flex shrink-0 flex-wrap">
<ButtonGroup <ButtonGroup
value={soundsFilter} value={soundsFilter}
@ -74,7 +74,8 @@ export function SoundsTab() {
></ButtonGroup> ></ButtonGroup>
<ImportSoundsButton onComplete={() => settingsMap.setKey('soundsFilter', 'user')} /> <ImportSoundsButton onComplete={() => settingsMap.setKey('soundsFilter', 'user')} />
</div> </div>
<div className="min-h-0 max-h-full grow overflow-auto font-mono text-sm break-normal pb-4">
<div className="min-h-0 max-h-full grow overflow-auto font-mono text-sm break-normal pb-2">
{soundEntries.map(([name, { data, onTrigger }]) => { {soundEntries.map(([name, { data, onTrigger }]) => {
return ( return (
<span <span

View file

@ -30,7 +30,8 @@ export const defaultSettings = {
isZen: false, isZen: false,
soundsFilter: 'all', soundsFilter: 'all',
patternFilter: 'community', patternFilter: 'community',
panelPosition: 'right', panelPosition: 'bottom',
isPanelPinned: true,
userPatterns: '{}', userPatterns: '{}',
audioDeviceName: defaultAudioDeviceName, audioDeviceName: defaultAudioDeviceName,
audioEngineTarget: audioEngineTargets.webaudio, audioEngineTarget: audioEngineTargets.webaudio,
@ -60,6 +61,7 @@ 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),
@ -77,6 +79,7 @@ export function useSettings() {
} }
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 setIsZen = (active) => settingsMap.setKey('isZen', !!active); export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);

View file

@ -52,6 +52,7 @@
} }
:root { :root {
--app-height: 100vh; --app-height: 100vh;
--app-width: 100vw;
} }
#console-tab { #console-tab {

View file

@ -34,6 +34,7 @@ module.exports = {
}, },
spacing: { spacing: {
'app-height': 'var(--app-height)', 'app-height': 'var(--app-height)',
'app-width': 'var(--app-width)'
}, },
typography(theme) { typography(theme) {
return { return {