fix(link): small design and ux tweaks

This commit is contained in:
Nicolas Meienberger 2024-02-06 19:55:44 +01:00 committed by Nicolas Meienberger
parent 4587c384da
commit 395cea1bd1
5 changed files with 47 additions and 40 deletions

View File

@ -3,7 +3,7 @@ import { z } from 'zod';
export const linkSchema = z.object({
id: z.number().nullable().optional(),
title: z.string().min(1).max(20),
description: z.string().min(1).max(50).nullable(),
description: z.string().min(0).max(50).nullable(),
url: z.string().url(),
iconUrl: z.string().url().or(z.string().max(0)).nullable(),
userId: z.number().nullable().optional(),

View File

@ -3,7 +3,7 @@ import { useForm } from 'react-hook-form';
import { Button } from '@/components/ui/Button';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader } from '@/components/ui/Dialog';
import { Input } from '@/components/ui/Input';
import React from 'react';
import React, { useEffect } from 'react';
import { addLinkAction } from '@/actions/custom-links/add-link-action';
import { editLinkAction } from '@/actions/custom-links/edit-link-action';
import toast from 'react-hot-toast';
@ -27,7 +27,7 @@ export const AddLinkModal: React.FC<AddLinkModalProps> = ({ isOpen, onClose, lin
const schema = z.object({
title: z.string().min(1).max(20),
description: z.string().min(1).max(50),
description: z.string().min(0).max(50).nullable(),
url: z.string().url(),
iconUrl: z.string().url().or(z.string().max(0)),
});
@ -39,14 +39,13 @@ export const AddLinkModal: React.FC<AddLinkModalProps> = ({ isOpen, onClose, lin
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: {
title: link?.title,
description: link?.description || '',
url: link?.url,
iconUrl: link?.iconUrl || '',
},
defaultValues: link,
});
useEffect(() => {
reset(link);
}, [link, reset]);
const addLinkMutation = useAction(addLinkAction, {
onError: (e) => {
if (e.serverError) toast.error(e.serverError);
@ -93,13 +92,17 @@ export const AddLinkModal: React.FC<AddLinkModalProps> = ({ isOpen, onClose, lin
<Input
disabled={mutationExecuting}
{...register('title')}
maxLength={20}
label={t('LINKS_FORM_LINK_TITLE')}
placeholder="Runtipi demo"
error={errors.title?.message}
/>
<Input
disabled={mutationExecuting}
type="text"
{...register('description')}
maxLength={50}
className="mt-3"
label={t('LINKS_FROM_LINK_DESCRIPTION')}
placeholder="My super app"
error={errors.description?.message}

View File

@ -1,11 +1,11 @@
.addLinkButton {
background: none;
padding-block: 0;
color: inherit;
border: none;
border-width: 0;
font: inherit;
cursor: pointer;
outline: inherit;
text-decoration: none;
color: inherit;
text-decoration: none;
}

View File

@ -37,7 +37,7 @@ export const LinkTile: React.FC<LinkTileProps> = ({ link: { id, title, descripti
<div data-testid={`link-tile-${title}`}>
<div className="card card-sm card-link">
<div className="card-body">
<div className="d-flex align-items-center">
<div className="d-flex align-items-center overflow-hidden">
<span className="me-3">
<AppLogo url={iconUrl || ''} size={60} />
</span>
@ -45,7 +45,7 @@ export const LinkTile: React.FC<LinkTileProps> = ({ link: { id, title, descripti
<div className="d-flex h-3 align-items-center">
<span className="h4 me-2 mb-1 fw-bolder">{title}</span>
</div>
{description?.length !== 0 && <div className="text-muted">{description}</div>}
{description?.length !== 0 && <div className="text-muted text-break">{description}</div>}
</div>
</div>
</div>

View File

@ -14,32 +14,36 @@ interface IProps {
disabled?: boolean;
value?: string;
readOnly?: boolean;
maxLength?: number;
}
export const Input = React.forwardRef<HTMLInputElement, IProps>(({ onChange, onBlur, name, label, placeholder, error, type = 'text', className, value, isInvalid, disabled, readOnly }, ref) => (
<div className={clsx(className)}>
{label && (
<label htmlFor={name} className="form-label">
{label}
</label>
)}
{/* eslint-disable-next-line jsx-a11y/no-redundant-roles */}
<input
suppressHydrationWarning
aria-label={name}
role="textbox"
disabled={disabled}
name={name}
id={name}
onBlur={onBlur}
onChange={onChange}
value={value}
type={type}
ref={ref}
className={clsx('form-control', { 'is-invalid is-invalid-lite': error || isInvalid })}
placeholder={placeholder}
readOnly={readOnly}
/>
{error && <div className="invalid-feedback">{error}</div>}
</div>
));
export const Input = React.forwardRef<HTMLInputElement, IProps>(
({ onChange, onBlur, name, label, placeholder, error, type = 'text', className, value, isInvalid, disabled, readOnly, maxLength }, ref) => (
<div className={clsx(className)}>
{label && (
<label htmlFor={name} className="form-label">
{label}
</label>
)}
{/* eslint-disable-next-line jsx-a11y/no-redundant-roles */}
<input
maxLength={maxLength}
suppressHydrationWarning
aria-label={name}
role="textbox"
disabled={disabled}
name={name}
id={name}
onBlur={onBlur}
onChange={onChange}
value={value}
type={type}
ref={ref}
className={clsx('form-control', { 'is-invalid is-invalid-lite': error || isInvalid })}
placeholder={placeholder}
readOnly={readOnly}
/>
{error && <div className="invalid-feedback">{error}</div>}
</div>
),
);