diff --git a/src/components/Icon.js b/src/components/Icon.js index c95c76d..ca64f43 100644 --- a/src/components/Icon.js +++ b/src/components/Icon.js @@ -1,6 +1,7 @@ import preact from 'preact' /** @jsx preact.h */ +import Grabber from '../assets/icons/octicons/grabber.svg?svgr' import Octoface from '../assets/icons/octicons/octoface.svg?svgr' import ChevronDown from '../assets/icons/octicons/chevron-down.svg?svgr' import TriangleRight from '../assets/icons/octicons/triangle-right.svg?svgr' @@ -21,6 +22,8 @@ import cx from '../utils/cx' function getSVGIconComponent(type) { switch (type) { + case 'grabber': + return Grabber case 'octoface': return Octoface case 'chevron-down': diff --git a/src/components/ResizeHandler.js b/src/components/ResizeHandler.js new file mode 100644 index 0000000..b95b5b0 --- /dev/null +++ b/src/components/ResizeHandler.js @@ -0,0 +1,60 @@ +import preact from 'preact' +/** @jsx preact.h */ + +import Icon from './Icon' + +export default class ResizeHandler extends preact.Component { + pointerDown = false + startX = 0 + delta = 0 + baseSize = this.props.baseSize + + subscribeEvents = () => { + window.addEventListener('mousemove', this.onPointerMove) + window.addEventListener('mouseup', this.onPointerUp) + } + + unsubscribeEvents = () => { + window.removeEventListener('mousemove', this.onPointerMove) + window.removeEventListener('mouseup', this.onPointerUp) + } + + /** + * + * @param {MouseEvent} e + * @memberof ResizeHandler + */ + onPointerDown = e => { + this.pointerDown = true + const { clientX } = e + this.startX = clientX + this.subscribeEvents() + } + + onPointerMove = e => { + if (!this.pointerDown) return + const { clientX } = e + const { onResize } = this.props + this.delta = this.startX - clientX + onResize(this.delta + this.baseSize) + } + + onPointerUp = () => { + this.pointerDown = false + this.baseSize += this.delta + this.unsubscribeEvents() + } + + render() { + const { style } = this.props + return ( +
+ +
+ ) + } +} \ No newline at end of file diff --git a/src/components/SideBar.js b/src/components/SideBar.js index d5b4b16..e0ea8e0 100644 --- a/src/components/SideBar.js +++ b/src/components/SideBar.js @@ -7,6 +7,7 @@ import FileExplorer from './FileExplorer' import ToggleShowButton from './ToggleShowButton' import MetaBar from './MetaBar' import SettingsBar from './SettingsBar' +import ResizeHandler from './ResizeHandler' import cx from '../utils/cx' import DOMHelper, { REPO_TYPE_PRIVATE } from '../utils/DOMHelper' @@ -15,8 +16,12 @@ import storageHelper from '../utils/storageHelper' import URLHelper from '../utils/URLHelper' import keyHelper from '../utils/keyHelper' +// initial width of side bar +const baseSize = 260 export default class SideBar extends preact.Component { state = { + // current width of side bar + size: 260, // whether Gitako side bar should be shown shouldShow: false, // whether show settings pane @@ -127,6 +132,12 @@ export default class SideBar extends preact.Component { this.setState({ toggleShowSideBarShortcut: shortcut }) } + onResize = size => { + this.setState({ + size, + }) + } + renderAccessDeniedError() { return (
@@ -155,6 +166,7 @@ export default class SideBar extends preact.Component { render() { const { + size, shouldShow, showSettings, hasAccessToken, @@ -166,16 +178,19 @@ export default class SideBar extends preact.Component { -
- {this.renderContent()} - +
+ +
+ {this.renderContent()} + +
) diff --git a/src/content.less b/src/content.less index 7d5a4a2..a01577a 100644 --- a/src/content.less +++ b/src/content.less @@ -2,16 +2,17 @@ @name: gitako; -@side-bar-width: 260px; -@github-content-width: 1280px; -@content-width: (@side-bar-width + @github-content-width); +@min-screen-width: 1280px; +@github-content-width: 1020px; +@side-bar-base-width: (@min-screen-width - @github-content-width); +@width-with-gitako: (@min-screen-width + @side-bar-base-width); .with-@{name}-spacing { - margin-left: @side-bar-width; - @media (min-width: @github-content-width) and (max-width: @content-width) { - margin-left: ~'calc(' @content-width ~' - 100vw)'; + margin-left: @side-bar-base-width; + @media (min-width: @min-screen-width) and (max-width: @width-with-gitako) { + margin-left: ~'calc(' @width-with-gitako ~' - 100vw)'; } - @media (min-width: @content-width) { + @media (min-width: @width-with-gitako) { margin-left: 0; } } @@ -68,10 +69,11 @@ } .@{name}-toggle-show-button-wrapper { - @animation-duration: 0.8s; + @animation-duration: 0.5s; position: fixed; top: 5px; left: 0; + transform: translate((@side-bar-base-width - 30px)); z-index: 2; display: inline-flex; justify-content: center; @@ -80,20 +82,23 @@ width: 30px; height: 30px; will-change: transform; - transform: translateX(225px); - transition: all @animation-duration ease; border: 1px solid transparent; - @media screen and (min-width: @content-width) { - transform: translateX(~'calc((100vw - ' @content-width ~') / 2 + ' 225px ~')'); + + @media screen and (max-width: @width-with-gitako) { + transition: all @animation-duration ease; + } + @media screen and (min-width: @width-with-gitako) { + transition: border-color, border-radius, background @animation-duration ease; + transform: translate(~'calc(50vw - (' @github-content-width ~') / 2 - 30px)'); } &.collapsed { border-color: #999999; - background: #f5f5f5; border-radius: 3px; + background: #f5f5f5; - @media screen and (max-width: @content-width) { - transform: translateX(5px); + @media screen and (max-width: @width-with-gitako) { + transform: translate(5px); } .action-icon { @@ -110,198 +115,229 @@ } .@{name} { - .@{name}-side-bar { + .@{name}-position-wrapper { position: fixed; top: 0; left: 0; - display: flex; - flex-direction: column; - width: @side-bar-width; height: 100vh; z-index: 2; - background: #fafbfc; - border-right: 1px solid #e1e4e8; - overflow: hidden; + min-width: @side-bar-base-width; + @media screen and (min-width: @min-screen-width) { + width: ~'calc((100vw - ' @github-content-width ~') / 2)'; + } - @media screen and (min-width: @content-width) { + .@{name}-resize-handler { + @resize-handler-width: 16px; + @side-bar-and-handler-width: (@min-screen-width + @resize-handler-width); + position: absolute; + top: 0; + display: none; + width: @resize-handler-width; + height: 100vh; + background: #fafbfc; + cursor: ew-resize; + user-select: none; border-left: 1px solid #e1e4e8; - left: ~'calc((100vw - ' @content-width ~') / 2)'; - } - &.hidden { - .hidden; - } - - .octicon { - transition: 0.3s ease; - color: rgba(3, 47, 98, 0.55); - vertical-align: initial; - } - - .octicon-wrapper { - display: inline-block; - width: 16px; - height: 16px; - text-align: center; - vertical-align: middle; - } - - .octicon-color { - color: rgba(3, 47, 98, 0.55); - } - - .@{name}-side-bar-content { - display: flex; - flex: 1; - flex-direction: column; - - .meta-bar { - position: relative; - padding: 10px; - padding-right: 30px; - font-size: 13px; - line-height: 20px; - color: #586069; - background-color: #f1f8ff; - border-bottom: 1px solid #c8e1ff; - - a { - // fix a weird bug: - // when gitako failed loading repo, cursor hovering in meta bar will be 'text' - cursor: pointer; - } - - .repo-name { - font-weight: bolder; - } - } - - .description { - padding: 4px 10px; - } - - .file-explorer { - width: 100%; - height: 100%; - outline: none; + @media screen and (min-width: @min-screen-width) { display: flex; - flex-direction: column; - flex-wrap: nowrap; - &.freeze { - filter: blur(1.5px) opacity(0.6) grayscale(0.9); - pointer-events: none; - } - - /* search input */ - .search-input-wrapper { - input[type='text'].form-control { - box-shadow: none; - width: 100%; - } - } - - .no-results { - padding: 0px 10px; - color: #666; - } - - .files { - flex: 1; - overflow: auto; - } - - .node-item-row { - background: #fff; - &:hover { - background: #f6f8fa; - } - &.focused { - background: #f0f0f6; - } - - .node-item { - word-wrap: normal; - word-break: break-all; - margin: 0; - color: #0366d6; - line-height: 20px; - padding: 6px 0; - cursor: pointer; - border-top: 1px solid #eaecef; - transition: all 0.5s ease; - - // folder icon rotate when expand - &.expanded .octicon.octicon-triangle-right { - transform: rotate(90deg); - } - - .node-item-name { - padding-left: 6px; - vertical-align: middle; - } - - .node-item:hover .node-item-name { - text-decoration: underline; - } - } - } - } - } - - .@{name}-settings-bar { - border-top: 1px solid #eaecef; - &-content { - padding: 0 10px; - &-section { - padding-bottom: 10px; - } - } - .access-token { - border-bottom: none; // prevent overwrite by github style - .hint { - color: #6a737d; - } - } - .access-token-input-control { - display: flex; - margin-top: 8px; - .access-token-input { - flex: 1; - box-shadow: none; - - &:disabled { - cursor: not-allowed; - } - } - } - .toggle-shortcut { - .hint { - color: #6a737d; - } - } - .toggle-shortcut-input-control { - display: flex; - margin-top: 8px; - .toggle-shortcut-input { - flex: 1; - box-shadow: none; - } - } - .placeholder-row { - display: flex; - justify-content: space-between; align-items: center; - padding: 6px 10px; + } - .show-settings-icon { - width: 20px; - height: 20px; - cursor: pointer; + .grabber-icon { + transform: rotate(90deg); + width: 12px; + } + } + .@{name}-side-bar { + position: absolute; + right: 0; + width: @side-bar-base-width; + max-width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background: #fafbfc; + border-right: 1px solid #e1e4e8; + overflow: hidden; + will-change: transform; + border-left: 1px solid #e1e4e8; + + &.hidden { + .hidden; + } + + .octicon { + transition: 0.3s ease; + color: rgba(3, 47, 98, 0.55); + vertical-align: initial; + } + + .octicon-wrapper { + display: inline-block; + width: 16px; + height: 16px; + text-align: center; + vertical-align: middle; + } + + .octicon-color { + color: rgba(3, 47, 98, 0.55); + } + + .@{name}-side-bar-content { + display: flex; + flex: 1; + flex-direction: column; + + .meta-bar { + position: relative; + padding: 10px; + padding-right: 30px; + font-size: 13px; + line-height: 20px; + color: #586069; + background-color: #f1f8ff; + border-bottom: 1px solid #c8e1ff; + + a { + // fix a weird bug: + // when gitako failed loading repo, cursor hovering in meta bar will be 'text' + cursor: pointer; + } + + .repo-name { + font-weight: bolder; + } } - .hide-settings-icon { - width: 15px; - height: 24px; - cursor: pointer; + + .description { + padding: 4px 10px; + } + + .file-explorer { + width: 100%; + height: 100%; + outline: none; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + &.freeze { + filter: blur(1.5px) opacity(0.6) grayscale(0.9); + pointer-events: none; + } + + /* search input */ + .search-input-wrapper { + input[type='text'].form-control { + box-shadow: none; + width: 100%; + } + } + + .no-results { + padding: 0px 10px; + color: #666; + } + + .files { + flex: 1; + overflow: auto; + } + + .node-item-row { + background: #fff; + &:hover { + background: #f6f8fa; + } + &.focused { + background: #f0f0f6; + } + + .node-item { + word-wrap: normal; + word-break: break-all; + margin: 0; + color: #0366d6; + line-height: 20px; + padding: 6px 0; + cursor: pointer; + border-top: 1px solid #eaecef; + transition: all 0.5s ease; + + // folder icon rotate when expand + &.expanded .octicon.octicon-triangle-right { + transform: rotate(90deg); + } + + .node-item-name { + padding-left: 6px; + vertical-align: middle; + } + + .node-item:hover .node-item-name { + text-decoration: underline; + } + } + } + } + } + + .@{name}-settings-bar { + border-top: 1px solid #eaecef; + &-content { + padding: 0 10px; + &-section { + padding-bottom: 10px; + } + } + .access-token { + border-bottom: none; // prevent overwrite by github style + .hint { + color: #6a737d; + } + } + .access-token-input-control { + display: flex; + margin-top: 8px; + .access-token-input { + flex: 1; + box-shadow: none; + + &:disabled { + cursor: not-allowed; + } + } + } + .toggle-shortcut { + .hint { + color: #6a737d; + } + } + .toggle-shortcut-input-control { + display: flex; + margin-top: 8px; + .toggle-shortcut-input { + flex: 1; + box-shadow: none; + } + } + .placeholder-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 10px; + + .show-settings-icon { + width: 20px; + height: 20px; + cursor: pointer; + } + .hide-settings-icon { + width: 15px; + height: 24px; + cursor: pointer; + } } } }