1
Fork 0

Add custom Modals

This commit is contained in:
viktorstrate 2021-06-18 14:48:22 +02:00
parent 51d2ac96c1
commit a60796e0ca
No known key found for this signature in database
GPG Key ID: 3F855605109C1E8A
15 changed files with 202 additions and 127 deletions

View File

@ -176,7 +176,7 @@
"title": "Slet bruger"
},
"password_reset": {
"description": "Ændre adgangskode for <1></1>",
"description": "Ændre adgangskode for <1>{{username}}</1>",
"form": {
"label": "Ny adgangskode",
"placeholder": "adgangskode",

View File

@ -176,7 +176,7 @@
"title": "Benutzer löschen"
},
"password_reset": {
"description": "Passwort von Benutzer <1></1> aktualisieren",
"description": "Passwort von Benutzer <1>{{username}}</1> aktualisieren",
"form": {
"label": "Neues Passwort",
"placeholder": "Passwort",

View File

@ -176,7 +176,7 @@
"title": "Delete user"
},
"password_reset": {
"description": "Change password for <1></1>",
"description": "Change password for <1>{{username}}</1>",
"form": {
"label": "New password",
"placeholder": "password",

View File

@ -176,7 +176,7 @@
"title": "Eliminar usuario"
},
"password_reset": {
"description": "Cambiar contraseña a <1></1>",
"description": "Cambiar contraseña a <1>{{username}}</1>",
"form": {
"label": "Nueva contraseña",
"placeholder": "contraseña",

View File

@ -176,7 +176,7 @@
"title": "Supprimer l'utilisateur"
},
"password_reset": {
"description": "Changer le mot de passe pour <1></1>",
"description": "Changer le mot de passe pour <1>{{username}}</1>",
"form": {
"label": "Nouveau mot de passe",
"placeholder": "mot de passe",

View File

@ -176,7 +176,7 @@
"title": "Cancella utente"
},
"password_reset": {
"description": "Cambia password per <1></1>",
"description": "Cambia password per <1>{{username}}</1>",
"form": {
"label": "Nuova password",
"placeholder": "password",

View File

@ -176,7 +176,7 @@
"title": "Usuń użytkownika"
},
"password_reset": {
"description": "Zmiana hasła dla <1></1>",
"description": "Zmiana hasła dla <1>{{username}}</1>",
"form": {
"label": "Nowe hasło",
"placeholder": "hasło",

View File

@ -176,7 +176,7 @@
"title": "Radera användare"
},
"password_reset": {
"description": "Ändra lösenord för <1></1>",
"description": "Ändra lösenord för <1>{{username}}</1>",
"form": {
"label": "Nytt lösenord",
"placeholder": "lösenord",

View File

@ -4,7 +4,7 @@ import Layout from '../../components/layout/Layout'
import styled from 'styled-components'
import { Link } from 'react-router-dom'
import SingleFaceGroup from './SingleFaceGroup/SingleFaceGroup'
import { Button, Icon, Input } from 'semantic-ui-react'
import { Button, TextField } from '../../primitives/form/Input'
import FaceCircleImage from './FaceCircleImage'
import useScrollPagination from '../../hooks/useScrollPagination'
import PaginateLoader from '../../components/PaginateLoader'
@ -91,7 +91,7 @@ export const FaceDetails = ({ group }: FaceDetailsProps) => {
const { t } = useTranslation()
const [editLabel, setEditLabel] = useState(false)
const [inputValue, setInputValue] = useState(group.label ?? '')
const inputRef = createRef<Input>()
const inputRef = createRef<HTMLInputElement>()
const [setGroupLabel, { loading }] = useMutation<
setGroupLabel,
@ -117,21 +117,11 @@ export const FaceDetails = ({ group }: FaceDetailsProps) => {
}
}, [loading])
const onKeyUp = (e: React.ChangeEvent<HTMLInputElement> & KeyboardEvent) => {
const onKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key == 'Escape') {
resetLabel()
return
}
if (e.key == 'Enter') {
setGroupLabel({
variables: {
groupID: group.id,
label: e.target.value == '' ? null : e.target.value,
},
})
return
}
}
let label
@ -145,20 +135,29 @@ export const FaceDetails = ({ group }: FaceDetailsProps) => {
<FaceLabel>
{group.label ?? t('people_page.face_group.unlabeled', 'Unlabeled')}
</FaceLabel>
<EditIcon name="pencil" />
{/* <EditIcon name="pencil" /> */}
</FaceDetailsButton>
)
} else {
label = (
<FaceDetailsButton labeled={!!group.label}>
<Input
<TextField
className="w-[160px]"
loading={loading}
ref={inputRef}
size="mini"
// size="mini"
placeholder={t('people_page.face_group.label_placeholder', 'Label')}
icon="arrow right"
// icon="arrow right"
value={inputValue}
onKeyUp={onKeyUp}
action={() =>
setGroupLabel({
variables: {
groupID: group.id,
label: inputValue == '' ? null : inputValue,
},
})
}
onKeyDown={onKeyUp}
onChange={e => setInputValue(e.target.value)}
onBlur={() => {
resetLabel()
@ -180,16 +179,16 @@ const FaceImagesCount = styled.span`
border-radius: 4px;
`
const EditIcon = styled(Icon)`
margin-left: 6px !important;
opacity: 0 !important;
// const EditIcon = styled(Icon)`
// margin-left: 6px !important;
// opacity: 0 !important;
transition: opacity 100ms;
// transition: opacity 100ms;
${FaceDetailsButton}:hover &, ${FaceDetailsButton}:focus-visible & {
opacity: 1 !important;
}
`
// ${FaceDetailsButton}:hover &, ${FaceDetailsButton}:focus-visible & {
// opacity: 1 !important;
// }
// `
type FaceGroupProps = {
group: myFaces_myFaceGroups
@ -251,13 +250,11 @@ const PeopleGallery = () => {
return (
<Layout title={t('title.people', 'People')}>
<Button
loading={recognizeUnlabeledLoading}
disabled={recognizeUnlabeledLoading}
onClick={() => {
recognizeUnlabeled()
}}
>
<Icon name="sync" />
{t(
'people_page.recognize_unlabeled_faces_button',
'Recognize unlabeled faces'

View File

@ -1,8 +1,9 @@
import React, { useState } from 'react'
import { gql, useMutation } from '@apollo/client'
import { Button, Form, Input, Modal, ModalProps } from 'semantic-ui-react'
import { Trans, useTranslation } from 'react-i18next'
import { settingsUsersQuery_user } from './__generated__/settingsUsersQuery'
import Modal from '../../../primitives/Modal'
import { TextField } from '../../../primitives/form/Input'
const changeUserPasswordMutation = gql`
mutation changeUserPassword($userId: ID!, $password: String!) {
@ -12,7 +13,7 @@ const changeUserPasswordMutation = gql`
}
`
interface ChangePasswordModalProps extends ModalProps {
interface ChangePasswordModalProps {
onClose(): void
open: boolean
user: settingsUsersQuery_user
@ -22,7 +23,6 @@ const ChangePasswordModal = ({
onClose,
user,
open,
...props
}: ChangePasswordModalProps) => {
const { t } = useTranslation()
const [passwordInput, setPasswordInput] = useState('')
@ -34,22 +34,42 @@ const ChangePasswordModal = ({
})
return (
<Modal open={open} {...props}>
<Modal.Header>
{t('settings.users.password_reset.title', 'Change password')}
</Modal.Header>
<Modal.Content>
<p>
<Modal
open={open}
onClose={onClose}
title={t('settings.users.password_reset.title', 'Change password')}
description={
<Trans t={t} i18nKey="settings.users.password_reset.description">
Change password for <b>{user.username}</b>
Change password for <b>{{ username: user.username }}</b>
</Trans>
</p>
<Form>
<Form.Field>
<label>
{t('settings.users.password_reset.form.label', 'New password')}
</label>
<Input
}
actions={[
{
key: 'cancel',
label: t('general.action.cancel', 'Cancel'),
onClick: () => onClose && onClose(),
},
{
key: 'change_password',
label: t(
'settings.users.password_reset.form.submit',
'Change password'
),
variant: 'positive',
onClick: () => {
changePassword({
variables: {
userId: user.id,
password: passwordInput,
},
})
},
},
]}
>
<div className="w-[360px]">
<TextField
label={t('settings.users.password_reset.form.label', 'New password')}
placeholder={t(
'settings.users.password_reset.form.placeholder',
'password'
@ -57,27 +77,7 @@ const ChangePasswordModal = ({
onChange={e => setPasswordInput(e.target.value)}
type="password"
/>
</Form.Field>
</Form>
</Modal.Content>
<Modal.Actions>
<Button onClick={() => onClose && onClose()}>
{t('general.action.cancel', 'Cancel')}
</Button>
<Button
positive
onClick={() => {
changePassword({
variables: {
userId: user.id,
password: passwordInput,
},
})
}}
>
{t('settings.users.password_reset.form.submit', 'Change password')}
</Button>
</Modal.Actions>
</div>
</Modal>
)
}

View File

@ -8,6 +8,7 @@ import {
TableRow,
TableBody,
TableFooter,
TableScrollWrapper,
} from '../../../primitives/Table'
import { useQuery, gql } from '@apollo/client'
import UserRow from './UserRow'
@ -53,6 +54,7 @@ const UsersTable = () => {
<div>
<SectionTitle>{t('settings.users.title', 'Users')}</SectionTitle>
<Loader active={loading} />
<TableScrollWrapper>
<Table className="w-full max-w-6xl">
<TableHeader>
<TableRow>
@ -60,7 +62,10 @@ const UsersTable = () => {
{t('settings.users.table.column_names.username', 'Username')}
</TableHeaderCell>
<TableHeaderCell>
{t('settings.users.table.column_names.photo_path', 'Photo path')}
{t(
'settings.users.table.column_names.photo_path',
'Photo path'
)}
</TableHeaderCell>
<TableHeaderCell>
{t(
@ -102,6 +107,7 @@ const UsersTable = () => {
</TableRow>
</TableFooter>
</Table>
</TableScrollWrapper>
</div>
)
}

View File

@ -202,7 +202,7 @@ const MorePopoverSectionPassword = ({
disabled={!activated}
type={passwordHidden ? 'password' : 'text'}
value={passwordInputValue}
className="mt-2"
className="mt-2 w-full"
onKeyDown={event => {
if (
event.shiftKey ||
@ -251,7 +251,7 @@ const MorePopover = ({ id, share, query }: MorePopoverProps) => {
<MorePopoverSectionPassword id={id} share={share} query={query} />
<div className="px-4 py-2 border-t border-gray-200 mt-2 mb-2">
<Checkbox label="Expiration date" />
<TextField className="mt-2" />
<TextField className="mt-2 w-full" />
</div>
</ArrowPopoverPanel>
</Popover.Panel>

View File

@ -0,0 +1,68 @@
import React from 'react'
import { Dialog } from '@headlessui/react'
import { Button } from './form/Input'
type ModalAction = {
key: string
label: string
variant?: 'negative' | 'positive' | 'default'
onClick(event: React.MouseEvent<HTMLButtonElement>): void
}
type ModalProps = {
title: string
description: React.ReactNode
children: React.ReactNode
actions: ModalAction[]
open: boolean
onClose(): void
}
const Modal = ({
title,
description,
children,
actions,
open,
onClose,
}: ModalProps) => {
const actionElms = actions.map(x => (
<Button
key={x.key}
onClick={e => x.onClick(e)}
variant={x.variant}
className="bg-white"
>
{x.label}
</Button>
))
return (
<Dialog
open={open}
onClose={onClose}
className="fixed z-40 inset-0 overflow-y-auto"
>
<div className="flex items-center justify-center min-h-screen">
<Dialog.Overlay className="fixed inset-0 bg-black opacity-30" />
<div className="fixed bg-white rounded max-w-[calc(100%-16px)] mx-auto rounded shadow-md border">
<div className="p-2">
<Dialog.Title className="text-xl mb-1">{title}</Dialog.Title>
<Dialog.Description className="text-sm mb-4">
{description}
</Dialog.Description>
{children}
</div>
<div className="bg-gray-50 p-2 flex gap-2 justify-end mt-4">
{actionElms}
</div>
</div>
</div>
</Dialog>
)
}
export default Modal

View File

@ -39,3 +39,7 @@ export const TableCell = styled.td.attrs({
export const TableHeaderCell = styled.th.attrs({
className: 'bg-gray-50 py-2 px-2 align-top font-semibold' as string,
})``
export const TableScrollWrapper = styled.div.attrs({
className: 'block overflow-x-auto whitespace-nowrap',
})``

View File

@ -77,7 +77,7 @@ export const TextField = forwardRef(
)
} else if (action) {
input = (
<div className="relative">
<div className="relative inline-block">
{input}
<button
disabled={disabled}