Skip to content

Commit 1b6fe0c

Browse files
committed
feat: select group avatars with the emoji picker
1 parent 4edd21a commit 1b6fe0c

File tree

9 files changed

+98
-71
lines changed

9 files changed

+98
-71
lines changed

site/src/@types/emoji-mart.d.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,17 @@ declare module "@emoji-mart/react" {
2828
| { unified: undefined; src: string }
2929
| { unified: string; src: undefined };
3030

31-
const EmojiPicker: React.FC<{
31+
export interface EmojiMartProps {
3232
set: "native" | "apple" | "facebook" | "google" | "twitter";
3333
theme: "dark" | "light";
3434
data: unknown;
3535
custom: CustomCategory[];
3636
emojiButtonSize?: number;
3737
emojiSize?: number;
3838
onEmojiSelect: (emoji: EmojiData) => void;
39-
}>;
39+
}
40+
41+
const EmojiMart: React.FC<EmojiMartProps>;
4042

41-
export default EmojiPicker;
43+
export default EmojiMart;
4244
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import EmojiMart, { type EmojiMartProps } from "@emoji-mart/react";
2+
import data from "@emoji-mart/data/sets/14/twitter.json";
3+
import { type FC } from "react";
4+
import icons from "theme/icons.json";
5+
6+
const custom = [
7+
{
8+
id: "icons",
9+
name: "Icons",
10+
emojis: icons.map((icon) => {
11+
const id = icon.split(".")[0];
12+
13+
return {
14+
id,
15+
name: id,
16+
keywords: id.split("-"),
17+
skins: [{ src: `/icon/${icon}` }],
18+
};
19+
}),
20+
},
21+
];
22+
23+
type EmojiPickerProps = Omit<
24+
EmojiMartProps,
25+
"custom" | "data" | "set" | "theme"
26+
>;
27+
28+
const EmojiPicker: FC<EmojiPickerProps> = (props) => {
29+
return (
30+
<EmojiMart
31+
theme="dark"
32+
set="twitter"
33+
data={data}
34+
custom={custom}
35+
{...props}
36+
/>
37+
);
38+
};
39+
40+
export default EmojiPicker;

site/src/components/IconField/IconField.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { action } from "@storybook/addon-actions";
2-
import IconField from "./IconField";
32
import type { Meta, StoryObj } from "@storybook/react";
3+
import { IconField } from "./IconField";
44

55
const meta: Meta<typeof IconField> = {
66
title: "components/IconField",

site/src/components/IconField/IconField.tsx

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@ import { css, Global, useTheme } from "@emotion/react";
22
import Button from "@mui/material/Button";
33
import InputAdornment from "@mui/material/InputAdornment";
44
import TextField, { type TextFieldProps } from "@mui/material/TextField";
5-
import Picker from "@emoji-mart/react";
6-
import { type FC } from "react";
5+
import { type FC, lazy, Suspense } from "react";
6+
import { Loader } from "components/Loader/Loader";
77
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
88
import { Stack } from "components/Stack/Stack";
9-
import data from "@emoji-mart/data/sets/14/twitter.json";
10-
import icons from "theme/icons.json";
119
import {
1210
Popover,
1311
PopoverContent,
1412
PopoverTrigger,
1513
} from "components/Popover/Popover";
14+
import { visuallyHidden } from "@mui/utils";
1615

1716
// See: https://github.com/missive/emoji-mart/issues/51#issuecomment-287353222
1817
const urlFromUnifiedCode = (unified: string) =>
@@ -22,24 +21,12 @@ type IconFieldProps = TextFieldProps & {
2221
onPickEmoji: (value: string) => void;
2322
};
2423

25-
const custom = [
26-
{
27-
id: "icons",
28-
name: "Icons",
29-
emojis: icons.map((icon) => {
30-
const id = icon.split(".")[0];
24+
const EmojiPicker = lazy(() => import("./EmojiPicker"));
3125

32-
return {
33-
id,
34-
name: id,
35-
keywords: id.split("-"),
36-
skins: [{ src: `/icon/${icon}` }],
37-
};
38-
}),
39-
},
40-
];
41-
42-
const IconField: FC<IconFieldProps> = ({ onPickEmoji, ...textFieldProps }) => {
26+
export const IconField: FC<IconFieldProps> = ({
27+
onPickEmoji,
28+
...textFieldProps
29+
}) => {
4330
if (
4431
typeof textFieldProps.value !== "string" &&
4532
typeof textFieldProps.value !== "undefined"
@@ -53,9 +40,9 @@ const IconField: FC<IconFieldProps> = ({ onPickEmoji, ...textFieldProps }) => {
5340
return (
5441
<Stack spacing={1}>
5542
<TextField
56-
{...textFieldProps}
5743
fullWidth
5844
label="Icon"
45+
{...textFieldProps}
5946
InputProps={{
6047
endAdornment: hasIcon ? (
6148
<InputAdornment
@@ -86,6 +73,18 @@ const IconField: FC<IconFieldProps> = ({ onPickEmoji, ...textFieldProps }) => {
8673
}}
8774
/>
8875

76+
<Global
77+
styles={css`
78+
em-emoji-picker {
79+
--rgb-background: ${theme.palette.background.paper};
80+
--rgb-input: ${theme.palette.primary.main};
81+
--rgb-color: ${theme.palette.text.primary};
82+
83+
// Hack to prevent the right side from being cut off
84+
width: 350px;
85+
}
86+
`}
87+
/>
8988
<Popover>
9089
{(popover) => (
9190
<>
@@ -98,35 +97,29 @@ const IconField: FC<IconFieldProps> = ({ onPickEmoji, ...textFieldProps }) => {
9897
id="emoji"
9998
css={{ marginTop: 0, ".MuiPaper-root": { width: "auto" } }}
10099
>
101-
<Global
102-
styles={css`
103-
em-emoji-picker {
104-
--rgb-background: ${theme.palette.background.paper};
105-
--rgb-input: ${theme.palette.primary.main};
106-
--rgb-color: ${theme.palette.text.primary};
107-
108-
// Hack to prevent the right side from being cut off
109-
width: 350px;
110-
}
111-
`}
112-
/>
113-
<Picker
114-
set="twitter"
115-
theme="dark"
116-
data={data}
117-
custom={custom}
118-
onEmojiSelect={(emoji) => {
119-
const value = emoji.src ?? urlFromUnifiedCode(emoji.unified);
120-
onPickEmoji(value);
121-
popover.setIsOpen(false);
122-
}}
123-
/>
100+
<Suspense fallback={<Loader />}>
101+
<EmojiPicker
102+
onEmojiSelect={(emoji) => {
103+
const value =
104+
emoji.src ?? urlFromUnifiedCode(emoji.unified);
105+
onPickEmoji(value);
106+
popover.setIsOpen(false);
107+
}}
108+
/>
109+
</Suspense>
124110
</PopoverContent>
125111
</>
126112
)}
127113
</Popover>
114+
115+
{/* This component takes a long time to load (easily several seconds), so we
116+
don't want to wait until the user actually clicks the button to start loading.
117+
Unfortunately, React doesn't provide an API to start warming a lazy component,
118+
so we just have to sneak it into the DOM, which is kind of annoying, but means
119+
that users shouldn't ever spend time waiting for it to load. */}
120+
<div css={{ ...visuallyHidden }}>
121+
<EmojiPicker onEmojiSelect={() => {}} />
122+
</div>
128123
</Stack>
129124
);
130125
};
131-
132-
export default IconField;

site/src/components/IconField/LazyIconField.tsx

Lines changed: 0 additions & 11 deletions
This file was deleted.

site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
HelpTooltipText,
2828
HelpTooltipTrigger,
2929
} from "components/HelpTooltip/HelpTooltip";
30-
import { LazyIconField } from "components/IconField/LazyIconField";
30+
import { IconField } from "components/IconField/IconField";
3131
import Link from "@mui/material/Link";
3232
import {
3333
HorizontalForm,
@@ -345,12 +345,11 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
345345
label="Description"
346346
/>
347347

348-
<LazyIconField
348+
<IconField
349349
{...getFieldHelpers("icon")}
350350
disabled={isSubmitting}
351351
onChange={onChangeTrimmed(form)}
352352
fullWidth
353-
label="Icon"
354353
onPickEmoji={(value) => form.setFieldValue("icon", value)}
355354
/>
356355
</FormFields>

site/src/pages/GroupsPage/CreateGroupPageView.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import TextField from "@mui/material/TextField";
22
import { CreateGroupRequest } from "api/typesGenerated";
33
import { FormFooter } from "components/FormFooter/FormFooter";
44
import { FullPageForm } from "components/FullPageForm/FullPageForm";
5+
import { IconField } from "components/IconField/IconField";
56
import { Margins } from "components/Margins/Margins";
67
import { Stack } from "components/Stack/Stack";
78
import { useFormik } from "formik";
@@ -39,6 +40,7 @@ export const CreateGroupPageView: FC<CreateGroupPageViewProps> = ({
3940
const getFieldHelpers = getFormHelpers<CreateGroupRequest>(form, formErrors);
4041
const onCancel = () => navigate("/groups");
4142

43+
console.log("i'll just cry then");
4244
return (
4345
<Margins>
4446
<FullPageForm title="Create group">
@@ -58,12 +60,14 @@ export const CreateGroupPageView: FC<CreateGroupPageViewProps> = ({
5860
fullWidth
5961
label="Display Name"
6062
/>
61-
<TextField
63+
<IconField
6264
{...getFieldHelpers("avatar_url")}
6365
onChange={onChangeTrimmed(form)}
64-
autoComplete="avatar url"
6566
fullWidth
6667
label="Avatar URL"
68+
onPickEmoji={(value) => (
69+
console.log(value), form.setFieldValue("avatar_url", value)
70+
)}
6771
/>
6872
</Stack>
6973
<FormFooter onCancel={onCancel} isLoading={isLoading} />

site/src/pages/GroupsPage/SettingsGroupPageView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Group } from "api/typesGenerated";
33
import { FormFooter } from "components/FormFooter/FormFooter";
44
import { FullPageForm } from "components/FullPageForm/FullPageForm";
55
import { Loader } from "components/Loader/Loader";
6-
import { LazyIconField } from "components/IconField/LazyIconField";
6+
import { IconField } from "components/IconField/IconField";
77
import { Margins } from "components/Margins/Margins";
88
import { useFormik } from "formik";
99
import { FC } from "react";
@@ -84,7 +84,7 @@ const UpdateGroupForm: FC<UpdateGroupFormProps> = ({
8484
label="Display Name"
8585
disabled={isEveryoneGroup(group)}
8686
/>
87-
<LazyIconField
87+
<IconField
8888
{...getFieldHelpers("avatar_url")}
8989
onChange={onChangeTrimmed(form)}
9090
fullWidth

site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
iconValidator,
1212
} from "utils/formUtils";
1313
import * as Yup from "yup";
14-
import { LazyIconField } from "components/IconField/LazyIconField";
14+
import { IconField } from "components/IconField/IconField";
1515
import {
1616
FormFields,
1717
FormSection,
@@ -126,7 +126,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
126126
rows={2}
127127
/>
128128

129-
<LazyIconField
129+
<IconField
130130
{...getFieldHelpers("icon")}
131131
disabled={isSubmitting}
132132
onChange={onChangeTrimmed(form)}

0 commit comments

Comments
 (0)