Work on sidebar share more popup

This commit is contained in:
viktorstrate
2021-06-04 22:29:58 +02:00
parent fc009ead1c
commit 9cb236df41
8 changed files with 145 additions and 48 deletions

29
ui/package-lock.json generated
View File

@@ -13,6 +13,7 @@
"@babel/core": "^7.14.0",
"@babel/preset-react": "^7.13.13",
"@babel/preset-typescript": "^7.13.0",
"@headlessui/react": "^1.2.0",
"@rollup/plugin-babel": "^5.3.0",
"@vitejs/plugin-react-refresh": "^1.3.3",
"autoprefixer": "^10.2.5",
@@ -22,6 +23,7 @@
"babel-plugin-i18next-extract": "^0.8.3",
"babel-plugin-styled-components": "^1.12.0",
"babel-plugin-transform-semantic-ui-react-imports": "^1.4.1",
"classnames": "^2.3.1",
"connect-history-api-fallback": "^1.6.0",
"copy-to-clipboard": "^3.3.1",
"esbuild": "^0.11.20",
@@ -1689,6 +1691,17 @@
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.0.tgz",
"integrity": "sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg=="
},
"node_modules/@headlessui/react": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.2.0.tgz",
"integrity": "sha512-19DkLz8gDgbi+WvkoTzi9vs0NK9TJf94vbYhMzB4LYJo03Kili0gmvXT9CiKZoxXZ7YAvy/b1U1oQKEnjWrqxw==",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=16"
}
},
"node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -4493,6 +4506,11 @@
"node": ">=0.10.0"
}
},
"node_modules/classnames": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
},
"node_modules/clean-stack": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@@ -15007,6 +15025,12 @@
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.0.tgz",
"integrity": "sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg=="
},
"@headlessui/react": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.2.0.tgz",
"integrity": "sha512-19DkLz8gDgbi+WvkoTzi9vs0NK9TJf94vbYhMzB4LYJo03Kili0gmvXT9CiKZoxXZ7YAvy/b1U1oQKEnjWrqxw==",
"requires": {}
},
"@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -17179,6 +17203,11 @@
}
}
},
"classnames": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
},
"clean-stack": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",

View File

@@ -13,6 +13,7 @@
"@babel/core": "^7.14.0",
"@babel/preset-react": "^7.13.13",
"@babel/preset-typescript": "^7.13.0",
"@headlessui/react": "^1.2.0",
"@rollup/plugin-babel": "^5.3.0",
"@vitejs/plugin-react-refresh": "^1.3.3",
"autoprefixer": "^10.2.5",
@@ -22,6 +23,7 @@
"babel-plugin-i18next-extract": "^0.8.3",
"babel-plugin-styled-components": "^1.12.0",
"babel-plugin-transform-semantic-ui-react-imports": "^1.4.1",
"classnames": "^2.3.1",
"connect-history-api-fallback": "^1.6.0",
"copy-to-clipboard": "^3.3.1",
"esbuild": "^0.11.20",

View File

@@ -2,6 +2,7 @@ import React, { useEffect } from 'react'
import { useMutation, useQuery, gql, useLazyQuery } from '@apollo/client'
import copy from 'copy-to-clipboard'
import { useTranslation } from 'react-i18next'
import { Popover } from '@headlessui/react'
import {
sidebareDeleteShare,
sidebareDeleteShareVariables,
@@ -31,6 +32,9 @@ import { ReactComponent as CopyIcon } from './icons/shareCopyIcon.svg'
import { ReactComponent as DeleteIcon } from './icons/shareDeleteIcon.svg'
import { ReactComponent as MoreIcon } from './icons/shareMoreIcon.svg'
import { ReactComponent as AddIcon } from './icons/shareAddIcon.svg'
import Checkbox from '../../primitives/form/Checkbox'
import { TextField } from '../../primitives/form/Input'
import styled from 'styled-components'
const SHARE_PHOTO_QUERY = gql`
query sidebarGetPhotoShares($id: ID!) {
@@ -236,6 +240,49 @@ const DELETE_SHARE_MUTATION = gql`
// )
// }
const ArrowPopoverPanel = styled.div.attrs({
className:
'absolute right-6 -top-3 bg-white rounded shadow-md border border-gray-200 w-[260px]',
})`
&::after {
content: '';
position: absolute;
top: 18px;
right: -7px;
width: 8px;
height: 14px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 14'%3E%3Cpolyline stroke-width='1' stroke='%23E2E2E2' fill='%23FFFFFF' points='1 0 7 7 1 14'%3E%3C/polyline%3E%3C/svg%3E");
}
`
const MorePopover = () => {
const { t } = useTranslation()
return (
<Popover className="relative">
<Popover.Button
className="align-middle p-1 ml-2"
title={t('sidebar.sharing.more', 'More')}
>
<MoreIcon />
</Popover.Button>
<Popover.Panel>
<ArrowPopoverPanel>
<div className="px-4 py-2">
<Checkbox label="Password protected" />
<TextField sizeVariant="small" className="mt-2" />
</div>
<div className="px-4 py-2 border-t border-gray-200 mt-2 mb-2">
<Checkbox label="Expiration date" />
<TextField sizeVariant="small" className="mt-2" />
</div>
</ArrowPopoverPanel>
</Popover.Panel>
</Popover>
)
}
type SidebarShareAlbumProps = {
id: string
}
@@ -364,7 +411,7 @@ const SidebarShare = ({
const optionsRows = shares.map(share => (
<tr key={share.token} className="border-gray-100 border-b border-t">
<td className="pl-4 w-full py-2">
<td className="pl-4 py-2 w-full">
<span className="text-[#585858] mr-2">
<LinkIcon className="inline-block mr-2" />
<span className="text-xs uppercase font-bold">
@@ -373,9 +420,9 @@ const SidebarShare = ({
</span>
<span className="text-sm">{share.token}</span>
</td>
<td className="pr-4 py-2 whitespace-nowrap text-[#5C6A7F] flex">
<td className="pr-6 py-2 whitespace-nowrap text-[#5C6A7F] flex">
<button
className="align-middle p-1 ml-1"
className="align-middle p-1 ml-2"
title={t('sidebar.sharing.copy_link', 'Copy Link')}
onClick={() => {
copy(`${location.origin}/share/${share.token}`)
@@ -387,17 +434,13 @@ const SidebarShare = ({
onClick={() => {
deleteShare({ variables: { token: share.token } })
}}
className="align-middle p-1 ml-1"
className="align-middle p-1 ml-2 hover:text-red-600 focus:text-red-600"
title={t('sidebar.sharing.delete', 'Delete')}
>
<DeleteIcon />
</button>
<button
className="align-middle p-1 ml-1"
title={t('sidebar.sharing.more', 'More')}
>
<MoreIcon />
</button>
<MorePopover />
{/* <ShareItemMoreDropdown share={share} id={id} isPhoto={isPhoto} /> */}
</td>
</tr>
@@ -405,8 +448,8 @@ const SidebarShare = ({
if (optionsRows.length == 0) {
optionsRows.push(
<tr key="no-shares">
<td colSpan={2}>
<tr key="no-shares" className="border-gray-100 border-b border-t">
<td colSpan={2} className="pl-4 py-2 italic text-gray-600">
{t('sidebar.sharing.no_shares_found', 'No shares found')}
</td>
</tr>
@@ -419,26 +462,28 @@ const SidebarShare = ({
{t('sidebar.sharing.title', 'Sharing options')}
</SidebarSectionTitle>
<div>
<table className="border-collapse">
{optionsRows}
<tr className="text-left border-gray-100 border-b border-t">
<td colSpan={2} className="pl-4 py-2">
<button
className="text-[#4ABF3C] font-bold uppercase text-xs"
disabled={loading}
onClick={() => {
shareItem({
variables: {
id,
},
})
}}
>
<AddIcon className="inline-block mr-2" />
<span>{t('sidebar.sharing.add_share', 'Add shares')}</span>
</button>
</td>
</tr>
<table className="border-collapse w-full">
<tbody>{optionsRows}</tbody>
<tfoot>
<tr className="text-left border-gray-100 border-b border-t">
<td colSpan={2} className="pl-4 py-2">
<button
className="text-[#4ABF3C] font-bold uppercase text-xs"
disabled={loading}
onClick={() => {
shareItem({
variables: {
id,
},
})
}}
>
<AddIcon className="inline-block mr-2" />
<span>{t('sidebar.sharing.add_share', 'Add shares')}</span>
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</SidebarSection>

View File

@@ -38,11 +38,15 @@ export const SidebarProvider = ({ children }: SidebarProviderProps) => {
pinned: boolean
}>({
content: null,
pinned: false,
pinned: true,
})
const updateSidebar = (content: React.ReactNode | null) => {
setState(state => ({ ...state, content }))
if (content) {
setState(state => ({ ...state, content }))
} else {
setState(state => ({ ...state, content: null, pinned: false }))
}
}
const setPinned = (pinned: boolean) => {
@@ -88,7 +92,7 @@ export const Sidebar = () => {
className={`fixed top-[72px] bg-white bottom-0 w-full overflow-y-auto transform transition-transform motion-reduce:transition-none ${
content == null && !pinned ? 'translate-x-full' : 'translate-x-0'
} ${
pinned ? 'mt-[62px]' : 'lg:shadow-separator'
pinned ? 'lg:mt-[62px]' : 'lg:shadow-separator'
} lg:w-[420px] lg:right-0 lg:top-0 lg:z-40`}
>
{content}

View File

@@ -16,13 +16,19 @@ const SidebarHeader = ({ title }: SidebarHeaderProps) => {
return (
<div className="m-2 flex items-center gap-2">
{!pinned && (
<button title="Close sidebar" onClick={() => updateSidebar(null)}>
<CloseIcon className="m-2" />
</button>
)}
<button
className={`${pinned ? 'lg:hidden' : ''}`}
title="Close sidebar"
onClick={() => updateSidebar(null)}
>
<CloseIcon className="m-2" />
</button>
<span className="flex-grow -mt-1">{title}</span>
<button title="Pin sidebar" onClick={() => setPinned(!pinned)}>
<button
className="hidden lg:block"
title="Pin sidebar"
onClick={() => setPinned(!pinned)}
>
<PinIcon className="m-2" />
</button>
</div>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg width="12px" height="14px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path stroke="none" stroke-width="1" fill="none" d="M5.01241676,3.99743718 L5.2123926,4.0051107 C6.08060614,4.06729045 6.87900923,4.50349496 7.40038252,5.20051071 C7.56578589,5.42163587 7.52061445,5.73497915 7.29948929,5.90038252 C7.07836413,6.06578589 6.76502085,6.02061445 6.59961748,5.79948929 C6.25203528,5.33481212 5.71976656,5.04400912 5.14095753,5.00255596 C4.62002941,4.96524811 4.10763194,5.1332244 3.71124008,5.46642286 L3.58355339,5.58355339 L2.0896441,7.0773559 C1.33169968,7.86211367 1.34253936,9.10952545 2.11400695,9.88099305 C2.84487099,10.6118571 4.00289823,10.6600523 4.78475751,10.0238225 L4.91144661,9.91144661 L5.76644661,9.05644661 C5.96170876,8.86118446 6.27829124,8.86118446 6.47355339,9.05644661 C6.64711974,9.23001296 6.66640489,9.49943736 6.53140884,9.6943055 L6.47355339,9.76355339 L5.6123559,10.6246441 C4.43521924,11.7615607 2.56410157,11.7453012 1.40690017,10.5880998 C0.295986831,9.47718649 0.236565536,7.70832791 1.24484904,6.52165836 L1.37644661,6.37644661 L2.87638252,4.87651071 C3.44442608,4.30826116 4.21444071,3.99361146 5.01241676,3.99743718 Z M10.5930998,1.41190017 C11.7040132,2.52281351 11.7634345,4.29167209 10.755151,5.47834164 L10.6235534,5.62355339 L9.12361748,7.12348929 C8.50823696,7.73909297 7.65582094,8.05706905 6.7876074,7.9948893 C5.91939386,7.93270955 5.12099077,7.49650504 4.59961748,6.79948929 C4.43421411,6.57836413 4.47938555,6.26502085 4.70051071,6.09961748 C4.92163587,5.93421411 5.23497915,5.97938555 5.40038252,6.20051071 C5.74796472,6.66518788 6.28023344,6.95599088 6.85904247,6.99744404 C7.37997059,7.03475189 7.89236806,6.8667756 8.28875992,6.53357714 L8.41644661,6.41644661 L9.9103559,4.9226441 C10.6683003,4.13788633 10.6574606,2.89047455 9.88599305,2.11900695 C9.15512901,1.38814291 7.99710177,1.3399477 7.21436478,1.97705267 L7.08752112,2.08958265 L6.22752112,2.94458265 C6.03169053,3.13927469 5.71510939,3.13835172 5.52041735,2.94252112 C5.34735776,2.76844949 5.32885818,2.49897001 5.46442178,2.30449627 L5.52247888,2.23541735 L6.3876441,1.3753559 C7.56478076,0.238439269 9.43589843,0.254698775 10.5930998,1.41190017 Z" fill="currentColor" fill-rule="nonzero"></path>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="10px" height="2px" viewBox="0 0 10 2" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg width="10px" height="14px" viewBox="0 0 10 2" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path stroke="none" stroke-width="1" fill="none" d="M0.646446609,0.646446609 C1.13807119,0.5 1.26307119,0.555964406 1.35355339,0.646446609 C1.44403559,0.736928813 1.5,0.861928813 1.5,1 C1.5,1.13807119 1.44403559,1.26307119 1.35355339,1.35355339 C1.26307119,1.44403559 1.13807119,1.5 1,1.5 C0.861928813,1.5 0.736928813,1.44403559 0.646446609,1.35355339 C0.555964406,1.26307119 0.5,1.13807119 0.5,1 C0.5,0.861928813 0.555964406,0.736928813 0.646446609,0.646446609 Z M4.64644661,0.646446609 C5.13807119,0.5 5.26307119,0.555964406 5.35355339,0.646446609 C5.44403559,0.736928813 5.5,0.861928813 5.5,1 C5.5,1.13807119 5.44403559,1.26307119 5.35355339,1.35355339 C5.26307119,1.44403559 5.13807119,1.5 5,1.5 C4.86192881,1.5 4.73692881,1.44403559 4.64644661,1.35355339 C4.55596441,1.26307119 4.5,1.13807119 4.5,1 C4.5,0.861928813 4.55596441,0.736928813 4.64644661,0.646446609 Z M8.64644661,0.646446609 C9.13807119,0.5 9.26307119,0.555964406 9.35355339,0.646446609 C9.44403559,0.736928813 9.5,0.861928813 9.5,1 C9.5,1.13807119 9.44403559,1.26307119 9.35355339,1.35355339 C9.26307119,1.44403559 9.13807119,1.5 9,1.5 C8.86192881,1.5 8.73692881,1.44403559 8.64644661,1.35355339 C8.55596441,1.26307119 8.5,1.13807119 8.5,1 C8.5,0.861928813 8.55596441,0.736928813 8.64644661,0.646446609 Z" stroke="currentColor"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,15 +1,20 @@
import React, { forwardRef } from 'react'
import classNames, { Argument as ClassNamesArg } from 'classnames'
type TextFieldProps = {
label?: string
error?: string
} & React.InputHTMLAttributes<HTMLInputElement>
className?: ClassNamesArg
sizeVariant?: 'default' | 'small'
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'className'>
export const TextField = forwardRef(
(
{ label, error, className, ...inputProps }: TextFieldProps,
{ label, error, className, sizeVariant, ...inputProps }: TextFieldProps,
ref: React.ForwardedRef<HTMLInputElement>
) => {
sizeVariant = sizeVariant ?? 'default'
let variant = 'bg-white border-gray-200 focus:border-blue-400'
if (error)
variant =
@@ -17,7 +22,11 @@ export const TextField = forwardRef(
const input = (
<input
className={`block border rounded-md h-10 w-full focus:ring-2 focus:outline-none px-2 ${variant}`}
className={classNames(
'block border rounded-md w-full focus:ring-2 focus:outline-none px-2',
variant,
sizeVariant == 'default' ? 'py-2' : 'py-1'
)}
{...inputProps}
ref={ref}
/>
@@ -28,7 +37,7 @@ export const TextField = forwardRef(
if (label) {
return (
<label className={`block my-4 ${className}`}>
<label className={classNames('block', className)}>
<span className="block text-xs uppercase font-semibold mb-1">
{label}
</span>
@@ -39,7 +48,9 @@ export const TextField = forwardRef(
}
return (
<div className={className}>
<div
className={classNames(className, sizeVariant == 'small' && 'text-sm')}
>
{input}
{errorElm}
</div>