Merge pull request 'feat: add settings page' (#124)
Reviewed-on: #124 Reviewed-by: Dominik <mail@dominikstahl.dev>
This commit is contained in:
commit
dbf9809c7b
27 changed files with 1491 additions and 565 deletions
|
@ -67,25 +67,27 @@ export function AppSidebar() {
|
|||
<>
|
||||
<Sidebar collapsible='icon' variant='sidebar'>
|
||||
<SidebarHeader className='overflow-hidden'>
|
||||
<Logo
|
||||
colorType='colored'
|
||||
logoType='combo'
|
||||
height={50}
|
||||
className='group-data-[collapsible=icon]:hidden min-w-[203px]'
|
||||
></Logo>
|
||||
<Logo
|
||||
colorType='colored'
|
||||
logoType='submark'
|
||||
height={50}
|
||||
className='group-data-[collapsible=]:hidden group-data-[mobile=true]/mobile:hidden'
|
||||
></Logo>
|
||||
<Link href='/home'>
|
||||
<Logo
|
||||
colorType='colored'
|
||||
logoType='combo'
|
||||
height={50}
|
||||
className='group-data-[collapsible=icon]:hidden min-w-[203px]'
|
||||
></Logo>
|
||||
<Logo
|
||||
colorType='colored'
|
||||
logoType='submark'
|
||||
height={50}
|
||||
className='group-data-[collapsible=]:hidden group-data-[mobile=true]/mobile:hidden'
|
||||
></Logo>
|
||||
</Link>
|
||||
</SidebarHeader>
|
||||
<SidebarContent className='grid grid-rows-[auto_1fr_auto] overflow-hidden'>
|
||||
<Collapsible defaultOpen className='group/collapsible'>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel asChild>
|
||||
<CollapsibleTrigger>
|
||||
<span className='flex items-center gap-2 text-xl font-label text-neutral-100'>
|
||||
<CollapsibleTrigger disabled>
|
||||
<span className='flex items-center gap-2 text-xl font-label text-disabled'>
|
||||
<Star className='size-8' />{' '}
|
||||
<span className='group-data-[collapsible=icon]:hidden'>
|
||||
Favorites
|
||||
|
|
|
@ -1,28 +1,56 @@
|
|||
import { Input, Textarea } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import React, { ForwardRefExoticComponent, RefAttributes } from 'react';
|
||||
import { Button } from '../ui/button';
|
||||
import { Eye, EyeOff, LucideProps } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export default function LabeledInput({
|
||||
type,
|
||||
label,
|
||||
subtext,
|
||||
placeholder,
|
||||
value,
|
||||
defaultValue,
|
||||
name,
|
||||
icon,
|
||||
variantSize = 'default',
|
||||
autocomplete,
|
||||
error,
|
||||
...rest
|
||||
}: {
|
||||
label: string;
|
||||
subtext?: string;
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
name?: string;
|
||||
icon?: ForwardRefExoticComponent<
|
||||
Omit<LucideProps, 'ref'> & RefAttributes<SVGSVGElement>
|
||||
>;
|
||||
variantSize?: 'default' | 'big' | 'textarea';
|
||||
autocomplete?: string;
|
||||
error?: string;
|
||||
} & React.InputHTMLAttributes<HTMLInputElement>) {
|
||||
const [passwordVisible, setPasswordVisible] = React.useState(false);
|
||||
const [inputValue, setInputValue] = React.useState(
|
||||
value || defaultValue || '',
|
||||
);
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setInputValue(e.target.value);
|
||||
if (rest.onChange) {
|
||||
rest.onChange(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='grid grid-cols-1 gap-1'>
|
||||
<Label htmlFor={name}>{label}</Label>
|
||||
{subtext && (
|
||||
<Label className='text-sm text-muted-foreground' htmlFor={name}>
|
||||
{subtext}
|
||||
</Label>
|
||||
)}
|
||||
{variantSize === 'textarea' ? (
|
||||
<Textarea
|
||||
placeholder={placeholder}
|
||||
|
@ -32,20 +60,48 @@ export default function LabeledInput({
|
|||
rows={3}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
defaultValue={value}
|
||||
id={name}
|
||||
name={name}
|
||||
className={
|
||||
variantSize === 'big'
|
||||
? 'h-12 file:h-10 text-lg gplaceholder:text-lg sm:text-2xl sm:placeholder:text-2xl'
|
||||
: ''
|
||||
}
|
||||
autoComplete={autocomplete}
|
||||
{...rest}
|
||||
/>
|
||||
<span className='relative'>
|
||||
<Input
|
||||
className={cn(
|
||||
type === 'password' ? 'pr-[50px]' : '',
|
||||
variantSize === 'big'
|
||||
? 'h-12 file:h-10 text-lg placeholder:text-lg sm:text-2xl sm:placeholder:text-2xl'
|
||||
: '',
|
||||
icon && inputValue === '' ? 'pl-10' : '',
|
||||
'transition-all duration-300 ease-in-out',
|
||||
)}
|
||||
type={passwordVisible ? 'text' : type}
|
||||
placeholder={placeholder}
|
||||
defaultValue={inputValue}
|
||||
id={name}
|
||||
name={name}
|
||||
autoComplete={autocomplete}
|
||||
{...rest}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
{icon && (
|
||||
<span
|
||||
className={cn(
|
||||
'absolute left-3 top-1/2 -translate-y-1/2 text-muted-input transition-all duration-300 ease-in-out',
|
||||
inputValue === ''
|
||||
? 'opacity-100 scale-100'
|
||||
: 'opacity-0 scale-75 pointer-events-none',
|
||||
)}
|
||||
>
|
||||
{React.createElement(icon)}
|
||||
</span>
|
||||
)}
|
||||
{type === 'password' && (
|
||||
<Button
|
||||
className='absolute right-0 top-0 w-[36px] h-[36px]'
|
||||
type='button'
|
||||
variant={'outline_muted'}
|
||||
onClick={() => setPasswordVisible((visible) => !visible)}
|
||||
>
|
||||
{passwordVisible ? <Eye /> : <EyeOff />}
|
||||
</Button>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{error && <p className='text-red-500 text-sm mt-1'>{error}</p>}
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue