Skip to content

Commit

Permalink
Fix responsive toolbar
Browse files Browse the repository at this point in the history
  • Loading branch information
jameskerr committed Mar 31, 2021
1 parent dd40189 commit 07dc20b
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 147 deletions.
19 changes: 10 additions & 9 deletions app/toolbar/action-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import React, {RefCallback} from "react"
import styled from "styled-components"
import ActionButton, {ActionButtonProps} from "./action-button"

type Justify = "center" | "flex-end"

export const GUTTER = 8

const BG = styled.div<{justify: Justify}>`
const BG = styled.div<{size: number}>`
display: flex;
justify-content: ${(p) => p.justify};
flex: 1;
flex: 0 1 auto;
overflow: hidden;
justify-content: flex-end;
width: ${(p) => p.size}px;
& > * {
margin-right: ${GUTTER}px;
&:last-child {
Expand All @@ -20,13 +21,13 @@ const BG = styled.div<{justify: Justify}>`

type Props = {
actions: ActionButtonProps[]
justify: Justify
innerRef: RefCallback<HTMLDivElement>
innerRef?: RefCallback<HTMLElement>
width?: number
}

export default function ActionButtons({actions, justify, innerRef}: Props) {
export default function ActionButtons({actions, width, innerRef}: Props) {
return (
<BG ref={innerRef} justify={justify}>
<BG ref={innerRef} size={width}>
{actions.map((props, i) => (
<ActionButton key={i} {...props} />
))}
Expand Down
29 changes: 29 additions & 0 deletions app/toolbar/action-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Icon from "app/core/Icon"
import React from "react"
import {showContextMenu} from "src/js/lib/System"
import styled from "styled-components"
import {toMenu} from "./action-button"
import {GUTTER} from "./action-buttons"
import Button from "./button"

const Menu = styled(Button)`
padding-right: 3px;
min-width: 22px;
flex-shrink: 0;
margin-left: ${GUTTER / 2}px;
.icon i svg {
width: 9px;
height: 9px;
}
`

export default function ActionMenu({actions}) {
if (actions.length === 0) return null
return (
<Menu
icon={<Icon name="double-chevron-right" />}
onClick={() => showContextMenu(toMenu(actions))}
/>
)
}
25 changes: 25 additions & 0 deletions app/toolbar/actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import MeasureLayer from "app/core/MeasureLayer"
import React from "react"
import ActionButtons from "./action-buttons"
import ActionMenu from "./action-menu"
import useVisibleActions from "./hooks/useVisibleActions"

/**
* We must measure how big this component will be if all the actions are visible
* to set correct flex basis. This is the "fullWidth" var you see below.
*/
export default function Actions({actions}) {
const {visible, hidden, fullWidth, setMeasure, setParent} = useVisibleActions(
actions
)

return (
<>
<MeasureLayer>
<ActionButtons actions={actions} innerRef={setMeasure} />
</MeasureLayer>
<ActionButtons actions={visible} innerRef={setParent} width={fullWidth} />
<ActionMenu actions={hidden} />
</>
)
}
35 changes: 35 additions & 0 deletions app/toolbar/hooks/useVisibleActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import useContentRect from "app/core/hooks/useContentRect"
import {useLayoutEffect, useState} from "react"
import useCallbackRef from "src/js/components/hooks/useCallbackRef"
import {GUTTER} from "../action-buttons"

export default function useVisibleActions(actions) {
const [visible, setVisible] = useState([])
const [hidden, setHidden] = useState([])
const [fullWidth, setFullWidth] = useState(0)
const [measure, setMeasure] = useCallbackRef()
const [rect, setParent] = useContentRect()
const actualWidth = Math.ceil(rect.width) + GUTTER

useLayoutEffect(() => {
let widths = []
if (measure) {
setFullWidth(measure.clientWidth)
widths = Array.from(measure.children).map((c) => c.clientWidth)
}

let offset = 0
let index = 0
for (let childWidth of widths) {
offset += childWidth + GUTTER
if (offset > actualWidth) break
index++
}
const copy = [...actions]
const nextHidden = copy.splice(index)
setVisible(copy)
setHidden(nextHidden)
}, [actions, measure, actualWidth])

return {visible, hidden, fullWidth, setMeasure, setParent}
}
96 changes: 0 additions & 96 deletions app/toolbar/responsive-actions.tsx

This file was deleted.

51 changes: 34 additions & 17 deletions app/toolbar/toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,39 @@ import SearchPageTitle from "src/js/components/SearchPageTitle"
import SpanControls from "src/js/components/Span/SpanControls"
import styled from "styled-components"
import {ActionButtonProps} from "./action-button"
import MainViewSwitch from "./main-view-switch"
import ResponsiveActions from "./responsive-actions"
import {GUTTER} from "./action-buttons"
import Actions from "./actions"

const Wrap = styled.div`
margin-bottom: 6px;
`

const Group = styled.div`
const Row = styled.div`
display: flex;
& > * {
margin-right: 8px;
&:last-child {
margin-right: 0;
}
}
justify-content: space-between;
align-items: center;
`

const Row = styled.div`
const Left = styled.div`
overflow: hidden;
min-width: 150px;
flex: 1 1 0;
margin-right: 12px;
align-items: center;
`

const Right = styled.div`
padding-top: 2px; // for the outline state to not get clipped
overflow: hidden;
display: flex;
justify-content: space-between;
flex: 0 1 auto;
width: min-content;
& > * {
margin-right: ${GUTTER}px;
&:last-child {
margin-right: 0;
}
}
`

Expand All @@ -33,16 +44,22 @@ type Props = {
actions: ActionButtonProps[]
}

/**
* The left side has a flex basis of 0 but will grow to fill the available space.
* The right side has a flex basis of min-content, will not grow, and will shrink
* allowing the toolbar buttons to collapse into a context menu.
*/
export function Toolbar({submit, actions}: Props) {
return (
<Wrap>
<SearchPageTitle />
<Row>
<Group>
<MainViewSwitch />
</Group>
<ResponsiveActions actions={actions} />
<SpanControls submit={submit} />
<Left>
<SearchPageTitle />
</Left>
<Right>
<Actions actions={actions} />
<SpanControls submit={submit} />
</Right>
</Row>
</Wrap>
)
Expand Down
2 changes: 0 additions & 2 deletions itest/lib/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ export const LOG = createLogger({
})
),
transports: [
new transports.Console({level: "info"}), // No idea how to make this runtime-configurable via command line. This has
// mkdir -p semantics so the environment variable seems OK enough.
new transports.File({
filename: path.join(itestDir(), "itest.log"),
level: "debug"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"start": "node scripts/start",
"test": "jest",
"test:api": "node scripts/test/api",
"itest": "jest --config=itest/config.json --runInBand --verbose",
"itest": "jest --config=itest/config.json --runInBand",
"lint": "eslint . --cache",
"format": "prettier **/*.{js,ts,tsx,scss} --write --loglevel warn",
"tsc": "tsc",
Expand Down
22 changes: 4 additions & 18 deletions src/js/components/SearchPageTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import {useSelector} from "react-redux"
import React from "react"
import {useSelector} from "react-redux"
import styled from "styled-components"

import brim from "../brim"
import {formatBytes} from "../lib/bytes"
import Current from "../state/Current"
import SpaceIcon from "./SpaceIcon"
import brim from "../brim"

const Wrap = styled.div`
min-width: 0;
height: 42px;
`
const Wrap = styled.div``

const Row = styled.div`
display: flex;
Expand All @@ -25,26 +20,18 @@ const StatsRow = styled(Row)``

const Title = styled.h2`
${(props) => props.theme.typography.labelBold}
text-shadow: 0 1px rgba(255, 255, 255, 0.5);
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`

const IconWrap = styled.div`
margin-right: 6px;
svg {
height: 13px;
width: 13px;
}
`

const Stat = styled.span`
${(props) => props.theme.typography.headingList}
color: var(--slate);
margin-right: 12px;
white-space: nowrap;
height: 12px;
`

export default function SearchPageTitle() {
Expand All @@ -57,7 +44,6 @@ export default function SearchPageTitle() {
return (
<Wrap>
<TitleRow>
<IconWrap>{<SpaceIcon type={space.getType()} />}</IconWrap>
<Title>{name}</Title>
</TitleRow>
<StatsRow>
Expand Down
7 changes: 3 additions & 4 deletions src/js/components/hooks/useCallbackRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import {useCallback, useState} from "react"

export default function useCallbackRef<T = Element>(): [
T,
(e: T | null) => void
(e: T | null) => T | null
] {
const [node, setNode] = useState(null)

const cb = useCallback((node) => {
if (node !== null) {
setNode(node)
}
if (node !== null) setNode(node)
return node
}, [])

return [node, cb]
Expand Down

0 comments on commit 07dc20b

Please sign in to comment.