Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitffa7722

Browse files
authored
feat: select group avatars with the emoji picker (#11395)
1 parentee2daed commitffa7722

File tree

10 files changed

+112
-76
lines changed

10 files changed

+112
-76
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-
constEmojiPicker:React.FC<{
31+
exportinterfaceEmojiMartProps{
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+
constEmojiMart:React.FC<EmojiMartProps>;
4042

41-
exportdefaultEmojiPicker;
43+
exportdefaultEmojiMart;
4244
}

‎site/src/components/ErrorBoundary/ErrorBoundary.tsx‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1-
import{Component,ReactNode,PropsWithChildren}from"react";
1+
import{Component,typeReactNode}from"react";
22
import{RuntimeErrorState}from"./RuntimeErrorState";
33

4-
typeErrorBoundaryProps=PropsWithChildren<unknown>;
4+
interfaceErrorBoundaryProps{
5+
fallback?:ReactNode;
6+
children:ReactNode;
7+
}
58

69
interfaceErrorBoundaryState{
710
error:Error|null;
811
}
912

1013
/**
1114
* Our app's Error Boundary
12-
* Read more about React Error Boundaries: https://reactjs.org/docs/error-boundaries.html
15+
* Read more about React Error Boundaries: https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary
1316
*/
1417
exportclassErrorBoundaryextendsComponent<
1518
ErrorBoundaryProps,
@@ -20,13 +23,15 @@ export class ErrorBoundary extends Component<
2023
this.state={error:null};
2124
}
2225

23-
staticgetDerivedStateFromError(error:Error):{error:Error}{
26+
staticgetDerivedStateFromError(error:Error):ErrorBoundaryState{
2427
return{ error};
2528
}
2629

2730
render():ReactNode{
2831
if(this.state.error){
29-
return<RuntimeErrorStateerror={this.state.error}/>;
32+
return(
33+
this.props.fallback??<RuntimeErrorStateerror={this.state.error}/>
34+
);
3035
}
3136

3237
returnthis.props.children;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
importEmojiMart,{typeEmojiMartProps}from"@emoji-mart/react";
2+
importdatafrom"@emoji-mart/data/sets/14/twitter.json";
3+
import{typeFC}from"react";
4+
importiconsfrom"theme/icons.json";
5+
6+
constcustom=[
7+
{
8+
id:"icons",
9+
name:"Icons",
10+
emojis:icons.map((icon)=>{
11+
constid=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+
typeEmojiPickerProps=Omit<
24+
EmojiMartProps,
25+
"custom"|"data"|"set"|"theme"
26+
>;
27+
28+
constEmojiPicker: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+
exportdefaultEmojiPicker;

‎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-
importIconFieldfrom"./IconField";
32
importtype{Meta,StoryObj}from"@storybook/react";
3+
import{IconField}from"./IconField";
44

55
constmeta:Meta<typeofIconField>={
66
title:"components/IconField",

‎site/src/components/IconField/IconField.tsx‎

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ import { css, Global, useTheme } from "@emotion/react";
22
importButtonfrom"@mui/material/Button";
33
importInputAdornmentfrom"@mui/material/InputAdornment";
44
importTextField,{typeTextFieldProps}from"@mui/material/TextField";
5-
importPickerfrom"@emoji-mart/react";
6-
import{typeFC}from"react";
5+
import{visuallyHidden}from"@mui/utils";
6+
import{typeFC,lazy,Suspense}from"react";
7+
import{Loader}from"components/Loader/Loader";
78
import{DropdownArrow}from"components/DropdownArrow/DropdownArrow";
89
import{Stack}from"components/Stack/Stack";
9-
importdatafrom"@emoji-mart/data/sets/14/twitter.json";
10-
importiconsfrom"theme/icons.json";
1110
import{
1211
Popover,
1312
PopoverContent,
@@ -22,24 +21,12 @@ type IconFieldProps = TextFieldProps & {
2221
onPickEmoji:(value:string)=>void;
2322
};
2423

25-
constcustom=[
26-
{
27-
id:"icons",
28-
name:"Icons",
29-
emojis:icons.map((icon)=>{
30-
constid=icon.split(".")[0];
24+
constEmojiPicker=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-
constIconField:FC<IconFieldProps>=({ onPickEmoji, ...textFieldProps})=>{
26+
exportconstIconField:FC<IconFieldProps>=({
27+
onPickEmoji,
28+
...textFieldProps
29+
})=>{
4330
if(
4431
typeoftextFieldProps.value!=="string"&&
4532
typeoftextFieldProps.value!=="undefined"
@@ -53,9 +40,9 @@ const IconField: FC<IconFieldProps> = ({ onPickEmoji, ...textFieldProps }) => {
5340
return(
5441
<Stackspacing={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,36 @@ 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-
constvalue=emoji.src??urlFromUnifiedCode(emoji.unified);
120-
onPickEmoji(value);
121-
popover.setIsOpen(false);
122-
}}
123-
/>
100+
<Suspensefallback={<Loader/>}>
101+
<EmojiPicker
102+
onEmojiSelect={(emoji)=>{
103+
constvalue=
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+
{/*
116+
- This component takes a long time to load (easily several seconds), so we
117+
don't want to wait until the user actually clicks the button to start loading.
118+
Unfortunately, React doesn't provide an API to start warming a lazy component,
119+
so we just have to sneak it into the DOM, which is kind of annoying, but means
120+
that users shouldn't ever spend time waiting for it to load.
121+
- Except we don't do it when running tests, because Jest doesn't define
122+
`IntersectionObserver`, and it would make them slower anyway. */}
123+
{process.env.NODE_ENV!=="test"&&(
124+
<divcss={{ ...visuallyHidden}}>
125+
<Suspense>
126+
<EmojiPickeronEmojiSelect={()=>{}}/>
127+
</Suspense>
128+
</div>
129+
)}
128130
</Stack>
129131
);
130132
};
131-
132-
exportdefaultIconField;

‎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
importLinkfrom"@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: 3 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";
@@ -58,12 +59,12 @@ export const CreateGroupPageView: FC<CreateGroupPageViewProps> = ({
5859
fullWidth
5960
label="Display Name"
6061
/>
61-
<TextField
62+
<IconField
6263
{...getFieldHelpers("avatar_url")}
6364
onChange={onChangeTrimmed(form)}
64-
autoComplete="avatar url"
6565
fullWidth
6666
label="Avatar URL"
67+
onPickEmoji={(value)=>form.setFieldValue("avatar_url",value)}
6768
/>
6869
</Stack>
6970
<FormFooteronCancel={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*asYupfrom"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)

[8]ページ先頭

©2009-2025 Movatter.jp