mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-24 15:25:46 +00:00
feat(FE-Storyless): update UI form UI repo
This commit is contained in:
+38
-23
@@ -1,6 +1,13 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ReactNode, RefObject, useCallback, useRef, useState } from 'react';
|
import {
|
||||||
|
ReactNode,
|
||||||
|
RefObject,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
|
|
||||||
export const useModal = () => {
|
export const useModal = () => {
|
||||||
@@ -8,31 +15,35 @@ export const useModal = () => {
|
|||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const openModal = useCallback(() => {
|
const openModal = useCallback(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
ref.current.showModal();
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
|
|
||||||
ref.current?.showModal();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closeModal = useCallback(() => {
|
const closeModal = useCallback(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
ref.current.close();
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
ref.current?.close();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggle = useCallback(() => {
|
const toggle = useCallback(() => {
|
||||||
if (open) {
|
open ? closeModal() : openModal();
|
||||||
closeModal();
|
|
||||||
} else {
|
|
||||||
openModal();
|
|
||||||
}
|
|
||||||
}, [open, closeModal, openModal]);
|
}, [open, closeModal, openModal]);
|
||||||
|
|
||||||
if (ref.current) {
|
// Gunakan useEffect agar event listener tidak didaftarkan berulang kali
|
||||||
ref.current.addEventListener('close', () => {
|
useEffect(() => {
|
||||||
closeModal();
|
const dialog = ref.current;
|
||||||
});
|
if (!dialog) return;
|
||||||
}
|
|
||||||
|
|
||||||
return { ref, open, setOpen, openModal, closeModal, toggle } as const;
|
const handleClose = () => setOpen(false);
|
||||||
|
dialog.addEventListener('close', handleClose);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
dialog.removeEventListener('close', handleClose);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { ref, open, openModal, closeModal, toggle } as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
@@ -46,15 +57,19 @@ interface ModalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Modal = ({ ref, children, closeOnBackdrop, className }: ModalProps) => {
|
const Modal = ({ ref, children, closeOnBackdrop, className }: ModalProps) => {
|
||||||
return (
|
const handleBackdropClick = (e: React.MouseEvent<HTMLDialogElement>) => {
|
||||||
<dialog ref={ref} className={cn('modal', className?.modal)}>
|
if (closeOnBackdrop && e.target === ref.current) {
|
||||||
<div className={cn('modal-box', className?.modalBox)}>{children}</div>
|
ref.current?.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
{closeOnBackdrop && (
|
return (
|
||||||
<form method='dialog' className='modal-backdrop'>
|
<dialog
|
||||||
<button>close</button>
|
ref={ref}
|
||||||
</form>
|
className={cn('modal', className?.modal)}
|
||||||
)}
|
onClick={handleBackdropClick}
|
||||||
|
>
|
||||||
|
<div className={cn('modal-box', className?.modalBox)}>{children}</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export interface TextAreaProps {
|
|||||||
label?: string;
|
label?: string;
|
||||||
bottomLabel?: string;
|
bottomLabel?: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
rows?: number;
|
||||||
value?: string | number;
|
value?: string | number;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
className?: {
|
className?: {
|
||||||
@@ -23,11 +24,8 @@ export interface TextAreaProps {
|
|||||||
required?: boolean;
|
required?: boolean;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
startAdornment?: ReactNode;
|
|
||||||
endAdornment?: ReactNode;
|
|
||||||
onChange?: ChangeEventHandler<HTMLTextAreaElement>;
|
onChange?: ChangeEventHandler<HTMLTextAreaElement>;
|
||||||
onBlur?: FocusEventHandler<HTMLTextAreaElement>;
|
onBlur?: FocusEventHandler<HTMLTextAreaElement>;
|
||||||
rows?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TextArea = ({
|
const TextArea = ({
|
||||||
@@ -35,20 +33,18 @@ const TextArea = ({
|
|||||||
bottomLabel,
|
bottomLabel,
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
|
rows = 3,
|
||||||
placeholder,
|
placeholder,
|
||||||
className,
|
className,
|
||||||
isError,
|
isError,
|
||||||
isValid,
|
isValid,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
startAdornment,
|
|
||||||
endAdornment,
|
|
||||||
disabled = false,
|
disabled = false,
|
||||||
required = false,
|
required = false,
|
||||||
onChange,
|
onChange,
|
||||||
onBlur,
|
onBlur,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
rows = 3,
|
|
||||||
}: TextAreaProps) => {
|
}: TextAreaProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -79,35 +75,34 @@ const TextArea = ({
|
|||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
{startAdornment && startAdornment}
|
|
||||||
|
|
||||||
<textarea
|
<div className='relative size-full'>
|
||||||
className={cn(
|
<textarea
|
||||||
'input h-auto px-4 py-2 text-base font-normal leading-6 w-full rounded outline-none! transition-all bg-white',
|
className={cn(
|
||||||
{
|
'textarea h-auto px-4 py-2 text-base font-normal leading-6 w-full rounded outline-none! transition-all bg-white',
|
||||||
'border-error': isError,
|
{
|
||||||
'border-success!': isValid,
|
'border-error': isError,
|
||||||
},
|
'border-success!': isValid,
|
||||||
className?.inputWrapper
|
},
|
||||||
|
className?.inputWrapper
|
||||||
|
)}
|
||||||
|
id={name}
|
||||||
|
name={name}
|
||||||
|
placeholder={placeholder}
|
||||||
|
value={value}
|
||||||
|
rows={rows}
|
||||||
|
onChange={onChange}
|
||||||
|
onBlur={onBlur}
|
||||||
|
disabled={disabled}
|
||||||
|
readOnly={readOnly}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isLoading && (
|
||||||
|
<div className='absolute right-3 bottom-3 z-10'>
|
||||||
|
{isLoading && <span className='loading loading-spinner' />}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
id={name}
|
</div>
|
||||||
name={name}
|
|
||||||
placeholder={placeholder}
|
|
||||||
value={value}
|
|
||||||
rows={rows}
|
|
||||||
onChange={onChange}
|
|
||||||
onBlur={onBlur}
|
|
||||||
disabled={disabled}
|
|
||||||
readOnly={readOnly}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{(isLoading || endAdornment) && (
|
|
||||||
<div className='flex flex-row gap-2'>
|
|
||||||
{isLoading && <span className='loading loading-spinner' />}
|
|
||||||
|
|
||||||
{endAdornment && endAdornment}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isError && bottomLabel && (
|
{!isError && bottomLabel && (
|
||||||
<p className='w-full text-sm opacity-60'>{bottomLabel}</p>
|
<p className='w-full text-sm opacity-60'>{bottomLabel}</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user