Skip to content

Commit

Permalink
Add initial avatar and image upload fields
Browse files Browse the repository at this point in the history
  • Loading branch information
raix committed Aug 27, 2024
1 parent 172cb12 commit 1522850
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 0 deletions.
60 changes: 60 additions & 0 deletions docs/components/forms/avatar-image-field.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# AvatarImageField

The `AvatarImageField` component is used to upload an image and display it as an avatar.

```tsx
import { AvatarImageField } from '@/ui/components/AvatarImageField';

export default function Example() {
return (
<AvatarImageField
name="image"
label="Photo"
placeholder="Upload an image"
avatarUrl={"/windcraft-logo.webp"}
initials={"W"}
acceptedFileTypes={["image/png"]}
/>
);
}
```

## Invalid

```tsx
import { AvatarImageField } from '@/ui/components/AvatarImageField';

export default function Example() {
return (
<AvatarImageField
name="image"
label="Photo"
placeholder="Upload an image"
avatarUrl={undefined}
initials={"W"}
acceptedFileTypes={["image/png"]}
isInvalid
/>
);
}
```

## Disabled

```tsx
import { AvatarImageField } from '@/ui/components/AvatarImageField';

export default function Example() {
return (
<AvatarImageField
name="image"
label="Photo"
placeholder="Upload an image"
avatarUrl={undefined}
initials={"W"}
acceptedFileTypes={["image/png"]}
isDisabled
/>
);
}
```
63 changes: 63 additions & 0 deletions docs/components/forms/image-field.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# ImageField

The `ImageField` component is used to upload an image.

```tsx
import { ImageField } from '@/ui/components/ImageField';

export default function Example() {
return (
<ImageField
name="image"
label="Image"
placeholder="Upload a logo (46x46px png)"
imageUrl={"/windcraft-logo.webp"}
height={46}
width={46}
acceptedFileTypes={["image/png"]}
/>
);
}
```

## Invalid

```tsx
import { ImageField } from '@/ui/components/ImageField';

export default function Example() {
return (
<ImageField
name="image"
label="Image"
placeholder="Upload a logo (46x46px png)"
imageUrl={undefined}
height={46}
width={46}
acceptedFileTypes={["image/png"]}
isInvalid
/>
);
}
```

## Disabled

```tsx
import { ImageField } from '@/ui/components/ImageField';

export default function Example() {
return (
<ImageField
name="image"
label="Image"
placeholder="Upload a logo (46x46px png)"
imageUrl={undefined}
height={46}
width={46}
acceptedFileTypes={["image/png"]}
isDisabled
/>
);
}
```
55 changes: 55 additions & 0 deletions ui/components/AvatarImageField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { FileTrigger, type FileTriggerProps, InputContext } from "react-aria-components";
import { Avatar } from "./Avatar";
import { Button } from "./Button";
import { useMemo, useState } from "react";

type AvatarImageFieldProps = {
name: string;
label?: string;
placeholder: string;
avatarUrl?: string;
initials: string;
isRound?: boolean;
} & Omit<FileTriggerProps, "onSelect">;

export function AvatarImageField({
name,
label,
placeholder = "Upload an image",
avatarUrl,
initials,
isRound = true,
acceptedFileTypes = ["image/png"],
...props
}: Readonly<AvatarImageFieldProps>) {
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [filename, setFilename] = useState<string | null>(null);
const inputContext = useMemo(() => ({ name: filename ? name : undefined }), [filename, name]);

return (
<div className="flex flex-col gap-3">
{label && <label>{label}</label>}
<InputContext.Provider value={inputContext}>
<div className="flex flex-row gap-4 items-center">
<FileTrigger
{...props}
acceptedFileTypes={acceptedFileTypes}
onSelect={(e) => {
if (e) {
const files = Array.from(e);
const file = files[0];
setFilename(file.name);
setPreviewUrl(URL.createObjectURL(file));
}
}}
>
<Button variant="icon" className={isRound ? "rounded-full" : "rounded-md"}>
<Avatar avatarUrl={previewUrl ?? avatarUrl} initials={initials} isRound={isRound} size="md" />
</Button>
</FileTrigger>
{filename ?? placeholder ?? ""}
</div>
</InputContext.Provider>
</div>
);
}
54 changes: 54 additions & 0 deletions ui/components/ImageField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { FileTrigger, type FileTriggerProps, InputContext } from "react-aria-components";
import { Button } from "./Button";
import { useMemo, useState } from "react";

type ImageFieldProps = {
name: string;
label?: string;
placeholder: string;
imageUrl: string;
height?: number;
width?: number;
} & Omit<FileTriggerProps, "onSelect">;

export function ImageField({
name,
label,
placeholder = "Upload an image",
imageUrl,
acceptedFileTypes = ["image/png"],
height,
width,
...props
}: Readonly<ImageFieldProps>) {
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [filename, setFilename] = useState<string | null>(null);
const inputContext = useMemo(() => ({ name: filename ? name : undefined }), [filename, name]);

return (
<div className="flex flex-col gap-3">
{label && <label>{label}</label>}
<InputContext.Provider value={inputContext}>
<div className="flex flex-row gap-4 items-center">
<FileTrigger
{...props}
acceptedFileTypes={acceptedFileTypes}
onSelect={(e) => {
if (e) {
const files = Array.from(e);
const file = files[0];
setFilename(file.name);
setPreviewUrl(URL.createObjectURL(file));
}
}}
>
<Button variant="icon" className="rounded-md w-fit h-fit p-1">
<img src={previewUrl ?? imageUrl} alt={name} height={height} width={width} />
</Button>
</FileTrigger>
{filename ?? placeholder ?? ""}
</div>
</InputContext.Provider>
</div>
);
}

0 comments on commit 1522850

Please sign in to comment.