diff --git a/src/components/material/Drawer.tsx b/src/components/material/Drawer.tsx index 7779b410..9328337b 100644 --- a/src/components/material/Drawer.tsx +++ b/src/components/material/Drawer.tsx @@ -1,6 +1,6 @@ -import type { JSXElement, ParentComponent } from 'solid-js' +import type { JSXElement, JSX, Component, ParentComponent } from 'solid-js' -import { useDimensions } from '~/utils/window' +import { useDimensions, useScreen } from '~/utils/window' type DrawerProps = { drawer: JSXElement @@ -9,46 +9,70 @@ type DrawerProps = { onClose?: () => void } +type OverlayProps = { + open: boolean + onClose?: () => void +} + const PEEK = 56 +const Overlay: Component = (props) => { + function handleOverlayClick(){ + if(props.onClose) props.onClose() + } + return ( +
+ ) +} + + const Drawer: ParentComponent = (props) => { + const screen = useScreen() const dimensions = useDimensions() + const isMobile = () => screen().mobile() + const drawerWidth = isMobile() ? dimensions().width - PEEK : 350 + const isOpen = () => !isMobile() || (isMobile() && props.open) - const isMobile = dimensions().width < 500 - const drawerWidth = isMobile ? dimensions().width - PEEK : 350 + function getContainerStyles (): JSX.CSSProperties { + return { + left: isOpen() ? `${drawerWidth}px` : 0, + width: isMobile() ? '100%' : `${dimensions().width - drawerWidth}px`, + } + } - const onClose = () => props.onClose?.() + function getNavbarStyles (): JSX.CSSProperties { + const opened = isOpen() + return { + left: opened ? 0 : `${-PEEK}px`, + opacity: opened ? 1 : 0.5, + width: `${drawerWidth}px`, + } + } return ( - <> +
+ style={getContainerStyles()} + class="absolute inset-y-0 flex w-screen flex-1 flex-col overflow-hidden bg-background transition-drawer duration-500"> {props.children} -
+ {isMobile() && isOpen() && ()}
- +
) } diff --git a/src/index.css b/src/index.css index e58a5b75..43f2a75a 100644 --- a/src/index.css +++ b/src/index.css @@ -2,6 +2,11 @@ @tailwind components; @tailwind utilities; +#root{ + width: 100vw; + height: 100vh; +} + @layer base { /* https://m3.material.io/styles/color/roles */ :root { diff --git a/src/pages/dashboard/Dashboard.tsx b/src/pages/dashboard/Dashboard.tsx index 5b73d818..95a53645 100644 --- a/src/pages/dashboard/Dashboard.tsx +++ b/src/pages/dashboard/Dashboard.tsx @@ -27,6 +27,7 @@ import DeviceActivity from './activities/DeviceActivity' import RouteActivity from './activities/RouteActivity' import SettingsActivity from './activities/SettingsActivity' import storage from '~/utils/storage' +import { useScreen } from '~/utils/window' const PairActivity = lazy(() => import('./activities/PairActivity')) @@ -42,11 +43,12 @@ const DashboardDrawer = (props: { onClose: () => void devices: Device[] | undefined }) => { + const screen = useScreen() return ( <> arrow_back} + leading={(screen().mobile() && arrow_back)} > comma connect diff --git a/src/pages/dashboard/activities/DeviceActivity.tsx b/src/pages/dashboard/activities/DeviceActivity.tsx index 81a47ec1..95cf8092 100644 --- a/src/pages/dashboard/activities/DeviceActivity.tsx +++ b/src/pages/dashboard/activities/DeviceActivity.tsx @@ -12,6 +12,7 @@ import { getDeviceName } from '~/utils/device' import RouteList from '../components/RouteList' import { DashboardContext } from '../Dashboard' +import { useScreen } from '~/utils/window' type DeviceActivityProps = { dongleId: string @@ -25,6 +26,7 @@ interface SnapshotResponse { } const DeviceActivity: VoidComponent = (props) => { + const screen = useScreen() const { toggleDrawer } = useContext(DashboardContext)! const [device] = createResource(() => props.dongleId, getDevice) @@ -104,59 +106,61 @@ const DeviceActivity: VoidComponent = (props) => { return ( <> menu} + leading={(screen().mobile() && menu)} trailing={settings} > {deviceName()} -
-
-
-
- }> -
- -
-
-
-
- void takeSnapshot()}>camera +
+
+
+
+
+ }> +
+ +
+
+
+
+ void takeSnapshot()}>camera +
-
-
- - {(image, index) => ( -
-
- {`Device -
- downloadSnapshot(image, index())} class="text-white">download - clearImage(index())} class="text-white">clear +
+ + {(image, index) => ( +
+
+ {`Device +
+ downloadSnapshot(image, index())} class="text-white">download + clearImage(index())} class="text-white">clear +
+ )} +
+ {snapshot().fetching && ( +
+
+
Loading snapshots...
+
)} - - {snapshot().fetching && ( -
-
-
Loading snapshots...
-
-
- )} - {snapshot().error && ( -
-
- Clear - Error: {snapshot().error} + {snapshot().error && ( +
+
+ Clear + Error: {snapshot().error} +
-
- )} -
-
- Routes - + )} +
+
+ Routes + +
diff --git a/src/utils/breakpoints.ts b/src/utils/breakpoints.ts new file mode 100644 index 00000000..b9682436 --- /dev/null +++ b/src/utils/breakpoints.ts @@ -0,0 +1,32 @@ +const MIN_SIZE = { + xs: 0, // phone + sm: 500, // tablet + md: 900, // small laptop + lg: 1200, // desktop + xl: 1536, // large screen +} + +export type Size = keyof typeof MIN_SIZE + +function up(size: Size) { + return `@media (min-width:${MIN_SIZE[size]}px)` +} + +function down(size: Size) { + const width = MIN_SIZE[size] + const ubound = Object.values(MIN_SIZE).find((val) => val > width) + if (ubound && ubound > 0) { + return `@media (max-width:${ubound}px)` + } else { + return `@media (max-width:${MIN_SIZE.xl * 1.2}px)` + } +} + +function only(size: Size) { + const excludeup = up(size).replace(/^@media( ?)/m, '') + const excludedown = down(size).replace(/^@media( ?)/m, '') + return `@media ${excludeup} and ${excludedown}` +} + +export default { values: MIN_SIZE, up, down, only } + diff --git a/src/utils/window.ts b/src/utils/window.ts index 6342919b..17c70351 100644 --- a/src/utils/window.ts +++ b/src/utils/window.ts @@ -1,8 +1,16 @@ -import { createSignal, onCleanup, onMount } from 'solid-js' +import { createSignal, createMemo, createEffect, onCleanup, onMount } from 'solid-js' import type { Accessor } from 'solid-js' +import breakpoints from './breakpoints' type Dimensions = { width: number; height: number } +const match = (query: string): boolean => { + if (typeof window === 'undefined') return true + if (typeof window.matchMedia !== 'function') return true + const media = window.matchMedia(query.replace(/^@media( ?)/m, '')) + return media.matches +} + export const getDimensions = (): Dimensions => { if (typeof window === 'undefined') return { width: 0, height: 0 } const { innerWidth: width, innerHeight: height } = window @@ -20,3 +28,29 @@ export const useDimensions = (): Accessor => { return dimensions } + +export function useMediaQuery(query: string) { + const [matches, setMatches] = createSignal(false) + + const listener = () => { + setMatches(match(query)) + } + + createEffect(() => { + listener() + window.addEventListener('resize', listener) + return () => window.removeEventListener('resize', listener) + }) + + return matches +} + +export function useScreen() { + const desktop = useMediaQuery(breakpoints.up('lg')) + const tablet = useMediaQuery(breakpoints.only('md')) + const mobile = useMediaQuery(breakpoints.down('sm')) + return createMemo(() => { + return {mobile: mobile, tablet: tablet, desktop: desktop} + }) +} +