Repository: material-ui
Issue: [docs] add avatar upload example
Labels: waiting for 👍,ready to take,enhancement,support: docs-feedback

### Related page

https://mui.com/material-ui/react-avatar/

### Kind of issue

Missing information

### Issue description

I recently needed to implement avatar upload inside form during sign up flow, I ended up with a bit hacky solution where I need to have visually hidden input nearby the clickable avatar, and that avatar will be a label, so that click on label will cause click on input. I also needed to put another hack in place so that input navigation with tab would work. It seems like quite popular usecase. Potentially adding section in the docs on how to implement it could save a lot of time and hacks in code to other people like me.

Just as the reference, this is what I end up with:
```tsx
import { memo, useId, useState } from 'react'
import type { AvatarProps, FormHelperTextProps } from '@mui/material'
import { Avatar, FormHelperText, Stack, styled } from '@mui/material'
import PersonIcon from '@mui/icons-material/Person'
import { avatarConstraints } from '../../model/contact-info-schema'
import { useFocus } from '@/shared/lib/react/useFocus'
import { useTab } from '@/shared/lib/react/useTab'

const VisuallyHiddenInput = styled('input')({
	clip: 'rect(0 0 0 0)',
	clipPath: 'inset(50%)',
	height: 1,
	overflow: 'hidden',
	position: 'absolute',
	bottom: 0,
	left: 0,
	whiteSpace: 'nowrap',
	width: 1,
})

const ClickableAvatar = styled(Avatar)(({ theme }) => ({
	width: 100,
	height: 100,
	cursor: 'pointer',
	transition: 'all .1s',
	'&[data-focused="true"]': {
		outline: `4px solid ${theme.palette.primary.main}`, // Replace with your desired style
		outlineOffset: '4px',
	},
	'&:hover': {
		filter: 'brightness(90%)',
	},
	'&:active': {
		scale: 0.9,
	},
}))

type ClickableAvatarProps = Omit<AvatarProps<'label'>, 'component'>

interface AvatarUploadProps {
	avatarProps?: ClickableAvatarProps
	inputProps?: React.InputHTMLAttributes<HTMLInputElement>
	helperTextProps?: FormHelperTextProps
}

export const AvatarUpload = memo(function AvatarUpload({
	avatarProps,
	inputProps,
	helperTextProps,
}: AvatarUploadProps) {
	const [imageSrc, setImageSrc] = useState<string>()

	const id = useId()
	const [isInputFocused, bindFocus] = useFocus()
	const { isTabLastKey } = useTab()

	const handleImageChange = (event: React.ChangeEvent<HTMLInputElement>) => {
		const file = event.target.files?.[0]
		if (file) {
			// Read the file as a data URL
			const reader = new FileReader()
			reader.onload = () => {
				setImageSrc(reader.result as string)
			}
			reader.readAsDataURL(file)
		}

		inputProps?.onChange?.(event)
	}

	return (
		<Stack alignItems={'center'}>
			<ClickableAvatar
				// @ts-expect-error it can't see component prop for some reason
				component='label'
				role={undefined}
				variant='circular'
				src={imageSrc}
				htmlFor={id}
				data-focused={isInputFocused && isTabLastKey.current}
				{...avatarProps}
			>
				<PersonIcon
					sx={{
						fontSize: '40px',
					}}
				/>
			</ClickableAvatar>
			<VisuallyHiddenInput
				id={id}
				type='file'
				accept={avatarConstraints.type.join(', ')}
				multiple={false}
				{...inputProps}
				{...bindFocus}
				onChange={handleImageChange}
			/>
			<FormHelperText {...helperTextProps} />
		</Stack>
	)
})
```
useFocus.ts (just tracks focus of the elem):
```tsx
import { useCallback, useMemo, useState } from 'react'

interface UseFocusBind {
	onBlur: () => void
	onFocus: () => void
}

export type UseFocusReturnType = [boolean, UseFocusBind]

/**
 * Tracks if an element is focused or not.
 * @returns {[boolean, {onBlur: () => void, onFocus: () => void}]}
 */
export const useFocus = (): UseFocusReturnType => {
	const [isFocused, setIsFocused] = useState(false)

	const onBlur = useCallback(() => {
		setIsFocused(false)
	}, [])

	const onFocus = useCallback(() => {
		setIsFocused(true)
	}, [])

	return useMemo(
		() => [isFocused, { onBlur, onFocus }],
		[isFocused, onBlur, onFocus],
	)
}
```

useTab(tracks if the tab key is the last pressed):
```tsx
import { useEffect, useRef } from 'react'

/**
 * Custom React Hook to track if the Tab key was the last key pressed.
 *
 * This hook sets up global event listeners for 'keydown' and 'mousedown' events.
 * It updates a ref `isTabLastKey` to determine if the Tab key was the last key pressed.
 * The use of a ref prevents unnecessary re-renders of your component when these events occur.
 *
 * @returns {Object} - An object containing:
 *   - `isTabLastKey` (Ref<boolean>): A ref that is `true` if the last key pressed was Tab, `false` otherwise.
 *
 * @example
 * const { isTabLastKey } = useTab();
 * // You can now use isTabLastKey.current in your component logic
 */
export const useTab = () => {
	const isTabLastKey = useRef(false)

	useEffect(() => {
		const handleKeyDown = (event: KeyboardEvent) => {
			if (event.key === 'Tab') {
				isTabLastKey.current = true
			} else {
				isTabLastKey.current = false
			}
		}

		const handleMouseDown = () => {
			isTabLastKey.current = false
		}

		window.addEventListener('keydown', handleKeyDown)

		window.addEventListener('mousedown', handleMouseDown)

		return () => {
			window.removeEventListener('keydown', handleKeyDown)
			window.removeEventListener('click', handleMouseDown)
		}
	}, [])

	return { isTabLastKey }
}
```




### Context

_No response_

**Search keywords**: mui avatar profile upload input hidden