Create custom checkbox
This commit is contained in:
parent
f2c32fa84f
commit
f76a2e88df
@ -12,7 +12,6 @@ import {
|
||||
} from '@/utility/date';
|
||||
import sleep from '@/utility/sleep';
|
||||
import { readStreamableValue } from 'ai/rsc';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Dispatch, SetStateAction, useRef, useState } from 'react';
|
||||
import { BiCheckCircle, BiImageAdd } from 'react-icons/bi';
|
||||
@ -120,15 +119,9 @@ export default function AdminBatchUploadActions({
|
||||
<Container padding="tight">
|
||||
<div className="w-full space-y-4 py-1">
|
||||
<div className="flex">
|
||||
<div className={clsx(
|
||||
'grow',
|
||||
tagErrorMessage ? 'text-error' : 'text-main',
|
||||
)}>
|
||||
<div className="grow text-main">
|
||||
{showBulkSettings
|
||||
? (
|
||||
tagErrorMessage ||
|
||||
`Apply to ${pluralize(storageUrls.length, 'upload')}`
|
||||
)
|
||||
? `Apply to ${pluralize(storageUrls.length, 'upload')}`
|
||||
: `Found ${pluralize(storageUrls.length, 'upload')}`}
|
||||
</div>
|
||||
<FieldSetWithStatus
|
||||
@ -140,7 +133,7 @@ export default function AdminBatchUploadActions({
|
||||
/>
|
||||
</div>
|
||||
{showBulkSettings && !actionErrorMessage &&
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-4 mb-6">
|
||||
<PhotoTagFieldset
|
||||
label="Tags"
|
||||
tags={tags}
|
||||
@ -149,20 +142,22 @@ export default function AdminBatchUploadActions({
|
||||
onError={setTagErrorMessage}
|
||||
readOnly={isAdding}
|
||||
/>
|
||||
<FieldSetWithStatus
|
||||
label="Favorite"
|
||||
type="checkbox"
|
||||
value={favorite}
|
||||
onChange={setFavorite}
|
||||
readOnly={isAdding}
|
||||
/>
|
||||
<FieldSetWithStatus
|
||||
label="Hidden"
|
||||
type="checkbox"
|
||||
value={hidden}
|
||||
onChange={setHidden}
|
||||
readOnly={isAdding}
|
||||
/>
|
||||
<div className="flex gap-8">
|
||||
<FieldSetWithStatus
|
||||
label="Favorite"
|
||||
type="checkbox"
|
||||
value={favorite}
|
||||
onChange={setFavorite}
|
||||
readOnly={isAdding}
|
||||
/>
|
||||
<FieldSetWithStatus
|
||||
label="Hidden"
|
||||
type="checkbox"
|
||||
value={hidden}
|
||||
onChange={setHidden}
|
||||
readOnly={isAdding}
|
||||
/>
|
||||
</div>
|
||||
</div>}
|
||||
<div className="space-y-2">
|
||||
<ProgressButton
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { useAppState } from '@/state/AppState';
|
||||
import SignInForm from '@/auth/SignInForm';
|
||||
import clsx from 'clsx';
|
||||
import clsx from 'clsx/lite';
|
||||
import PhotoUploadWithStatus from '@/photo/PhotoUploadWithStatus';
|
||||
|
||||
export default function SignInOrUploadClient({
|
||||
|
||||
53
src/components/Checkbox.tsx
Normal file
53
src/components/Checkbox.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import clsx from 'clsx/lite';
|
||||
import { InputHTMLAttributes, ReactNode, Ref } from 'react';
|
||||
import { ImCheckmark } from 'react-icons/im';
|
||||
|
||||
const boxStyles = clsx(
|
||||
'relative',
|
||||
'inline-flex items-center justify-center',
|
||||
'size-5 rounded-md border',
|
||||
);
|
||||
|
||||
export default function Checkbox({
|
||||
ref,
|
||||
className,
|
||||
accessory,
|
||||
type: _type,
|
||||
...props
|
||||
}: InputHTMLAttributes<HTMLInputElement> & {
|
||||
ref?: Ref<HTMLInputElement>
|
||||
accessory?: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<span className={clsx(
|
||||
'relative inline-flex items-center justify-center',
|
||||
'size-5',
|
||||
'group-has-active:opacity-70',
|
||||
)}>
|
||||
{accessory
|
||||
? accessory
|
||||
: props.checked
|
||||
? <span className={clsx(
|
||||
boxStyles,
|
||||
'border-transparent',
|
||||
'bg-blue-600',
|
||||
)}>
|
||||
<ImCheckmark className="text-white text-[11px]" />
|
||||
</span>
|
||||
: <span className={clsx(
|
||||
boxStyles,
|
||||
'border-gray-300 dark:border-gray-700',
|
||||
'bg-gray-100 dark:bg-gray-700/25',
|
||||
)} />}
|
||||
<input
|
||||
ref={ref}
|
||||
type="checkbox"
|
||||
className={clsx(
|
||||
'absolute inset-0 invisible',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { Ref } from 'react';
|
||||
import { Ref, InputHTMLAttributes } from 'react';
|
||||
import { useFormStatus } from 'react-dom';
|
||||
import Spinner from './Spinner';
|
||||
import { clsx } from 'clsx/lite';
|
||||
@ -8,6 +8,7 @@ import { FieldSetType, AnnotatedTag } from '@/photo/form';
|
||||
import TagInput from './TagInput';
|
||||
import { FiChevronDown } from 'react-icons/fi';
|
||||
import { parameterize } from '@/utility/string';
|
||||
import Checkbox from './Checkbox';
|
||||
|
||||
export default function FieldSetWithStatus({
|
||||
id: _id,
|
||||
@ -33,7 +34,6 @@ export default function FieldSetWithStatus({
|
||||
inputRef,
|
||||
accessory,
|
||||
hideLabel,
|
||||
checkboxAccessory,
|
||||
}: {
|
||||
id?: string
|
||||
label: string
|
||||
@ -58,59 +58,58 @@ export default function FieldSetWithStatus({
|
||||
inputRef?: Ref<HTMLInputElement>
|
||||
accessory?: React.ReactNode
|
||||
hideLabel?: boolean
|
||||
checkboxAccessory?: React.ReactNode
|
||||
}) {
|
||||
const id = _id || parameterize(label);
|
||||
|
||||
const { pending } = useFormStatus();
|
||||
|
||||
const renderInput =
|
||||
<input
|
||||
ref={inputRef}
|
||||
id={id}
|
||||
name={id}
|
||||
type={type}
|
||||
value={value}
|
||||
checked={type === 'checkbox' ? value === 'true' : undefined}
|
||||
placeholder={placeholder}
|
||||
onChange={e => onChange?.(type === 'checkbox'
|
||||
? e.target.value === 'true' ? 'false' : 'true'
|
||||
: e.target.value)}
|
||||
spellCheck={spellCheck}
|
||||
autoComplete="off"
|
||||
autoCapitalize={!capitalize ? 'off' : undefined}
|
||||
readOnly={readOnly || pending || loading}
|
||||
disabled={type === 'checkbox' && (
|
||||
const inputProps: InputHTMLAttributes<HTMLInputElement> = {
|
||||
id,
|
||||
name: id,
|
||||
type,
|
||||
value,
|
||||
checked: type === 'checkbox' ? value === 'true' : undefined,
|
||||
placeholder,
|
||||
onChange: e => onChange?.(type === 'checkbox'
|
||||
? e.target.value === 'true' ? 'false' : 'true'
|
||||
: e.target.value),
|
||||
spellCheck,
|
||||
autoComplete: 'off',
|
||||
autoCapitalize: !capitalize ? 'off' : undefined,
|
||||
readOnly: readOnly || pending || loading,
|
||||
disabled: type === 'checkbox' && (
|
||||
readOnly || pending || loading
|
||||
),
|
||||
className: clsx(
|
||||
(
|
||||
type === 'text' ||
|
||||
type === 'email' ||
|
||||
type === 'password'
|
||||
) && 'w-full',
|
||||
type === 'checkbox' && (
|
||||
readOnly || pending || loading
|
||||
)}
|
||||
className={clsx(
|
||||
(
|
||||
type === 'text' ||
|
||||
type === 'email' ||
|
||||
type === 'password'
|
||||
) && 'w-full',
|
||||
type === 'checkbox' && (
|
||||
readOnly || pending || loading
|
||||
) && 'opacity-50 cursor-not-allowed',
|
||||
Boolean(error) && 'error',
|
||||
)}
|
||||
/>;
|
||||
) && 'opacity-50 cursor-not-allowed',
|
||||
Boolean(error) && 'error',
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
type === 'hidden'
|
||||
? renderInput
|
||||
type === 'hidden'
|
||||
? <input ref={inputRef} {...inputProps} />
|
||||
: <div className={clsx(
|
||||
// For managing checkbox active state
|
||||
'group',
|
||||
'space-y-1',
|
||||
type === 'checkbox' && 'flex items-center gap-2',
|
||||
type === 'checkbox' && 'flex items-center gap-3',
|
||||
className,
|
||||
)}>
|
||||
{!hideLabel && label &&
|
||||
{!hideLabel &&
|
||||
<label
|
||||
className={clsx(
|
||||
'flex flex-wrap gap-x-2 items-center select-none',
|
||||
type === 'checkbox' && 'order-2 pt-[4px] ml-1',
|
||||
)}
|
||||
htmlFor={id}
|
||||
className={clsx(
|
||||
'inline-flex flex-wrap gap-x-2 items-center select-none',
|
||||
type === 'checkbox' && 'order-2 m-0',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{note && !error &&
|
||||
@ -132,7 +131,7 @@ export default function FieldSetWithStatus({
|
||||
<span className="text-gray-400 dark:text-gray-600">
|
||||
Required
|
||||
</span>}
|
||||
{loading &&
|
||||
{loading && type !== 'checkbox' &&
|
||||
<span className="translate-y-[1.5px]">
|
||||
<Spinner />
|
||||
</span>}
|
||||
@ -202,11 +201,18 @@ export default function FieldSetWithStatus({
|
||||
Boolean(error) && 'error',
|
||||
)}
|
||||
/>
|
||||
: type === 'checkbox' && checkboxAccessory
|
||||
? <span className="w-[13px]">
|
||||
{checkboxAccessory}
|
||||
</span>
|
||||
: renderInput}
|
||||
: type === 'checkbox'
|
||||
? <Checkbox
|
||||
ref={inputRef}
|
||||
accessory={loading && <Spinner
|
||||
className="translate-y-[0.5px]"
|
||||
/>}
|
||||
{...inputProps}
|
||||
/>
|
||||
: <input
|
||||
ref={inputRef}
|
||||
{...inputProps}
|
||||
/>}
|
||||
{accessory && <div>
|
||||
{accessory}
|
||||
</div>}
|
||||
|
||||
@ -2,7 +2,6 @@ import FieldSetWithStatus from '@/components/FieldSetWithStatus';
|
||||
import { ComponentProps, useEffect, useState } from 'react';
|
||||
import { getPhotosNeedingRecipeTitleCountAction } from '../actions';
|
||||
import { FilmSimulation } from '@/simulation';
|
||||
import Spinner from '@/components/Spinner';
|
||||
|
||||
export default function ApplyRecipeTitleGloballyCheckbox({
|
||||
photoId,
|
||||
@ -22,7 +21,7 @@ export default function ApplyRecipeTitleGloballyCheckbox({
|
||||
}) {
|
||||
const [matchingPhotosCount, setMatchingPhotosCount] = useState<number>();
|
||||
|
||||
const isLoading = matchingPhotosCount === undefined;
|
||||
const loading = matchingPhotosCount === undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (recipeTitle && hasRecipeTitleChanged && recipeData && simulation) {
|
||||
@ -38,21 +37,18 @@ export default function ApplyRecipeTitleGloballyCheckbox({
|
||||
onMatchResults((matchingPhotosCount ?? 0) > 0);
|
||||
}, [matchingPhotosCount, onMatchResults]);
|
||||
|
||||
const shouldShowFieldSet = isLoading || matchingPhotosCount > 0;
|
||||
const shouldShowFieldSet = loading || matchingPhotosCount > 0;
|
||||
|
||||
return (
|
||||
shouldShowFieldSet
|
||||
? <FieldSetWithStatus {...{
|
||||
...props,
|
||||
label: isLoading
|
||||
label: loading
|
||||
? 'Scanning photos for matching recipes ...'
|
||||
: `Apply title to ${matchingPhotosCount} matching photos`,
|
||||
type: 'checkbox',
|
||||
readOnly: isLoading,
|
||||
className: '-mt-4 translate-x-[1px]',
|
||||
checkboxAccessory: isLoading
|
||||
? <Spinner className="translate-y-[1.5px]" />
|
||||
: null,
|
||||
className: '-mt-4 translate-x-[4px]',
|
||||
loading,
|
||||
}} />
|
||||
: null
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user