mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
feat: redesign toggle show button
This commit is contained in:
parent
02e3337037
commit
6d7610349e
6 changed files with 154 additions and 61 deletions
4
src/assets/icons/png.d.ts
vendored
Normal file
4
src/assets/icons/png.d.ts
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
declare module '*.png' {
|
||||
const content: string
|
||||
export default content
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import { cx } from 'utils/cx'
|
|||
import { parseURLSearch } from 'utils/general'
|
||||
import { usePJAX } from 'utils/hooks/usePJAX'
|
||||
import * as keyHelper from 'utils/keyHelper'
|
||||
import { Icon } from './Icon'
|
||||
|
||||
const RawGitako: React.FC<Props & ConnectorState> = function RawGitako(props) {
|
||||
const configContext = useConfigs()
|
||||
|
|
@ -93,16 +94,22 @@ const RawGitako: React.FC<Props & ConnectorState> = function RawGitako(props) {
|
|||
return (
|
||||
<div className={'gitako-side-bar'}>
|
||||
<Portal into={logoContainerElement}>
|
||||
<ToggleShowButton
|
||||
error={error}
|
||||
shouldShow={shouldShow}
|
||||
toggleShowSideBar={toggleShowSideBar}
|
||||
/>
|
||||
{!shouldShow && (
|
||||
<ToggleShowButton error={error} onClick={error ? undefined : toggleShowSideBar} />
|
||||
)}
|
||||
</Portal>
|
||||
<Resizable className={cx({ hidden: error || !shouldShow })} baseSize={baseSize}>
|
||||
<div className={'gitako-side-bar-body'}>
|
||||
<div className={'gitako-side-bar-content'}>
|
||||
{metaData && <MetaBar metaData={metaData} />}
|
||||
<div className={'header'}>
|
||||
{metaData ? <MetaBar metaData={metaData} /> : <div />}
|
||||
|
||||
<div className={'close-side-bar-button-position'}>
|
||||
<button className={'close-side-bar-button'} onClick={toggleShowSideBar}>
|
||||
<Icon className={'action-icon'} type={'x'} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{errorDueToAuth ? (
|
||||
<AccessDeniedError hasToken={Boolean(accessToken)} />
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -1,24 +1,67 @@
|
|||
import { Icon } from 'components/Icon'
|
||||
import iconSrc from 'assets/icons/Gitako.png'
|
||||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import * as React from 'react'
|
||||
import { useDebounce, useWindowSize } from 'react-use'
|
||||
import { cx } from 'utils/cx'
|
||||
|
||||
type Props = {
|
||||
error?: string
|
||||
shouldShow: boolean
|
||||
toggleShowSideBar: React.MouseEventHandler
|
||||
}
|
||||
} & Pick<React.HTMLAttributes<HTMLButtonElement>, 'onClick'>
|
||||
|
||||
export function ToggleShowButton({ error, onClick }: Props) {
|
||||
const ref = React.useRef<HTMLButtonElement>(null)
|
||||
const config = useConfigs()
|
||||
const [distance, setDistance] = React.useState(config.val.toggleButtonVerticalDistance)
|
||||
const { height } = useWindowSize()
|
||||
const buttonHeight = 42
|
||||
React.useEffect(() => {
|
||||
// make sure it is inside viewport
|
||||
if (height - buttonHeight < distance) {
|
||||
setDistance(Math.max(0, height - buttonHeight))
|
||||
}
|
||||
}, [height, distance])
|
||||
React.useLayoutEffect(() => {
|
||||
if (ref.current) {
|
||||
ref.current.style.top = distance + 'px'
|
||||
}
|
||||
}, [distance])
|
||||
|
||||
// updating context
|
||||
useDebounce(
|
||||
() => config.set({ toggleButtonVerticalDistance: distance }), // too slow
|
||||
100,
|
||||
[distance],
|
||||
)
|
||||
|
||||
export function ToggleShowButton({ error, shouldShow, toggleShowSideBar }: Props) {
|
||||
return (
|
||||
<div
|
||||
<button
|
||||
ref={ref}
|
||||
className={cx('gitako-toggle-show-button-wrapper', {
|
||||
collapsed: !shouldShow || error,
|
||||
error,
|
||||
})}
|
||||
onClick={error ? undefined : toggleShowSideBar}
|
||||
onClick={onClick}
|
||||
draggable
|
||||
onDragStart={event => {
|
||||
hideDragPreview(event)
|
||||
}}
|
||||
onDrag={e => {
|
||||
if (e.clientY !== 0) {
|
||||
// It will be 0 when release pointer
|
||||
setDistance(e.clientY - buttonHeight / 2)
|
||||
}
|
||||
}}
|
||||
title={'You can drag me'}
|
||||
>
|
||||
<Icon className={'action-icon'} type={shouldShow ? 'x' : 'octoface'} />
|
||||
<img draggable={false} src={iconSrc} />
|
||||
{error && <span className={'error-message'}>{error}</span>}
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function hideDragPreview(event: React.DragEvent<HTMLButtonElement>) {
|
||||
const img = new Image()
|
||||
const EMPTY_IMAGE_BASE64 =
|
||||
'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs='
|
||||
img.src = EMPTY_IMAGE_BASE64
|
||||
event.dataTransfer.setDragImage(img, 0, 0)
|
||||
}
|
||||
|
|
|
|||
121
src/content.scss
121
src/content.scss
|
|
@ -139,54 +139,52 @@ $minimal-z-index: max($github-header-z-index, $github-pull-request-float-header-
|
|||
visibility: hidden;
|
||||
}
|
||||
|
||||
.#{$name}-toggle-show-button-wrapper {
|
||||
$animation-duration: 0.5s;
|
||||
position: fixed;
|
||||
top: 5px;
|
||||
left: 0;
|
||||
z-index: $minimal-z-index;
|
||||
@mixin icon-button {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border: 1px solid transparent;
|
||||
will-change: transform;
|
||||
transform: translate(calc(var(--gitako-width) - 30px));
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
transition: transform $animation-duration ease;
|
||||
// disable position transition when resizing sidebar
|
||||
&.resizing {
|
||||
transition: none;
|
||||
@mixin button-color {
|
||||
border: none;
|
||||
background: $gray-000;
|
||||
&:hover {
|
||||
background: $gray-200;
|
||||
}
|
||||
&:active {
|
||||
background: $gray-300;
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
border-color: $border-gray;
|
||||
border-radius: 3px;
|
||||
background: $bg-gray;
|
||||
transform: translate(5px);
|
||||
|
||||
.action-icon {
|
||||
color: $blue;
|
||||
.#{$name}-toggle-show-button-wrapper {
|
||||
@include icon-button;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
left: -16px;
|
||||
z-index: $minimal-z-index;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
transition: left ease 0.5s;
|
||||
&:hover {
|
||||
left: -12px;
|
||||
img {
|
||||
filter: drop-shadow(0 0 2px #888888);
|
||||
width: 92%;
|
||||
height: 92%;
|
||||
}
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
color: $gray;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
transition: all $animation-duration ease;
|
||||
.octicon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
.action-icon {
|
||||
color: $red;
|
||||
}
|
||||
img {
|
||||
filter: drop-shadow(0 0 1px #aaaaaa);
|
||||
transition: all ease 0.3s;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
|
|
@ -286,13 +284,46 @@ $minimal-z-index: max($github-header-z-index, $github-pull-request-float-header-
|
|||
max-height: calc(100vh - 34px); // temporary fix for layout issue occurred since Chrome v76
|
||||
min-height: 0; // make content shrinkable
|
||||
|
||||
.meta-bar {
|
||||
position: relative; // prevent overlap by outline of other elements
|
||||
min-height: 53px; // GitHub header height if login
|
||||
flex-shrink: 0;
|
||||
padding: 5px 10px;
|
||||
padding-right: 30px; // space for toggle button
|
||||
background: $bg-blue-light;
|
||||
.header {
|
||||
$button-size: 32px;
|
||||
position: relative;
|
||||
|
||||
.meta-bar {
|
||||
position: relative; // prevent overlap by outline of other elements
|
||||
min-height: 53px; // GitHub header height if login
|
||||
flex-shrink: 0;
|
||||
padding: 5px 10px;
|
||||
padding-right: $button-size; // space for toggle button
|
||||
background: $gray-000;
|
||||
}
|
||||
|
||||
.close-side-bar-button-position {
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
.close-side-bar-button {
|
||||
@include icon-button;
|
||||
@include button-color;
|
||||
width: $button-size;
|
||||
height: $button-size;
|
||||
border-radius: $button-size;
|
||||
transition: background linear 0.3s;
|
||||
|
||||
.action-icon {
|
||||
color: $gray;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
.octicon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export type Config = {
|
|||
copySnippetButton: boolean
|
||||
intelligentToggle: boolean | null // `null` stands for intelligent, boolean for sidebar open status
|
||||
icons: 'rich' | 'dim' | 'native'
|
||||
toggleButtonVerticalDistance: number
|
||||
}
|
||||
|
||||
export enum configKeys {
|
||||
|
|
@ -20,6 +21,7 @@ export enum configKeys {
|
|||
copySnippetButton = 'copySnippetButton',
|
||||
intelligentToggle = 'intelligentToggle',
|
||||
icons = 'icons',
|
||||
toggleButtonVerticalDistance = 'toggleButtonVerticalDistance',
|
||||
}
|
||||
|
||||
const defaultConfigs: Config = {
|
||||
|
|
@ -31,6 +33,7 @@ const defaultConfigs: Config = {
|
|||
copySnippetButton: true,
|
||||
intelligentToggle: null,
|
||||
icons: 'rich',
|
||||
toggleButtonVerticalDistance: 80,
|
||||
}
|
||||
|
||||
const configKeyArray = Object.values(configKeys)
|
||||
|
|
|
|||
|
|
@ -104,6 +104,11 @@ module.exports = {
|
|||
loader: ['json-loader'],
|
||||
include: [srcPath],
|
||||
},
|
||||
{
|
||||
test: /\.png$/,
|
||||
loader: ['url-loader'],
|
||||
include: [srcPath],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins,
|
||||
|
|
|
|||
Loading…
Reference in a new issue