Skip to content

Commit b163b22

Browse files
authored
Create message builder page (#399)
1 parent a90342d commit b163b22

76 files changed

Lines changed: 8035 additions & 132 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

services/backend-api/client/package-lock.json

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

services/backend-api/client/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"@tanstack/react-table": "^8.9.3",
2323
"dayjs": "^1.11.11",
2424
"eslint-config-airbnb-typescript": "^18.0.0",
25+
"focus-trap": "^7.6.5",
2526
"framer-motion": "^5.5.6",
2627
"highlight.js": "^11.7.0",
2728
"i18next": "^21.6.12",
@@ -36,6 +37,7 @@
3637
"react-select": "^5.10.0",
3738
"react-table": "^7.8.0",
3839
"react-textarea-autosize": "^8.3.3",
40+
"react-virtuoso": "^4.14.1",
3941
"simple-markdown": "^0.7.3",
4042
"twemoji": "^14.0.2",
4143
"uuid": "^9.0.1",

services/backend-api/client/src/components/DiscordView/index.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const MessageBody = ({
4242
}: {
4343
compactMode?: boolean;
4444
username?: string;
45-
content?: string;
45+
content?: string | null;
4646
webhookMode?: boolean;
4747
}) => {
4848
if (compactMode) {
@@ -143,15 +143,17 @@ const DiscordView = ({
143143
avatar_url,
144144
error,
145145
messages,
146+
excludeHeader,
146147
}: {
148+
excludeHeader?: boolean;
147149
compactMode?: boolean;
148150
darkTheme?: boolean;
149151
webhookMode?: boolean;
150152
username?: string;
151153
avatar_url?: string;
152154
error?: string;
153155
messages: Array<{
154-
content?: string;
156+
content?: string | null;
155157
embeds?: DiscordViewEmbed[];
156158
components?: Array<{
157159
type: number;
@@ -171,11 +173,16 @@ const DiscordView = ({
171173
<div className={cls}>
172174
<ErrorHeader error={error} />
173175
<DiscordViewWrapper darkTheme={darkTheme}>
174-
<div className={`message-group hide-overflow ${compactMode ? "compact" : ""}`}>
175-
<Avatar url={avatar_url} compactMode={compactMode} />
176+
<div
177+
className={`message-group hide-overflow ${compactMode ? "compact" : ""}`}
178+
style={excludeHeader ? { padding: 0, margin: 0 } : {}}
179+
>
180+
{!excludeHeader && <Avatar url={avatar_url} compactMode={compactMode} />}
176181
<div className="comment">
177182
<div className="message first">
178-
<CozyMessageHeader username={username} compactMode={compactMode} />
183+
{excludeHeader ? null : (
184+
<CozyMessageHeader username={username} compactMode={compactMode} />
185+
)}
179186
{messages.map(({ content: thisContent, embeds: thisEmbeds, components }, index) => (
180187
<div className="message-text" key={index}>
181188
<MessageBody
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import React, { ReactNode } from "react";
2+
import {
3+
VStack,
4+
Textarea,
5+
Input,
6+
Button,
7+
FormControl,
8+
FormLabel,
9+
FormErrorMessage,
10+
FormHelperText,
11+
} from "@chakra-ui/react";
12+
import { AddIcon } from "@chakra-ui/icons";
13+
import { InsertPlaceholderDialog } from "../../pages/Previewer/InsertPlaceholderDialog";
14+
15+
interface Props {
16+
value: string;
17+
onChange: (value: string) => void;
18+
label: string;
19+
placeholder?: string;
20+
error?: string;
21+
helperText?: ReactNode;
22+
rows?: number;
23+
isInvalid?: boolean;
24+
as?: "input" | "textarea";
25+
isRequired?: boolean;
26+
}
27+
28+
export const InputWithInsertPlaceholder: React.FC<Props> = ({
29+
value,
30+
onChange,
31+
label,
32+
placeholder = "Enter text content",
33+
error,
34+
helperText,
35+
rows = 4,
36+
isInvalid = false,
37+
as = "textarea",
38+
isRequired,
39+
}) => {
40+
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
41+
const inputRef = React.useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);
42+
43+
const handleInsertPlaceholder = React.useCallback(
44+
(tag: string) => {
45+
if (inputRef.current) {
46+
const input = inputRef.current;
47+
const start = input.selectionStart || 0;
48+
const end = input.selectionEnd || 0;
49+
50+
const currentValue = input.value;
51+
52+
if (!start && !end) {
53+
const newValue = currentValue + tag;
54+
onChange(newValue);
55+
56+
input.setSelectionRange(newValue.length, newValue.length);
57+
} else {
58+
const newValue = currentValue.substring(0, start) + tag + currentValue.substring(end);
59+
60+
onChange(newValue);
61+
input.setSelectionRange(start + tag.length, start + tag.length);
62+
}
63+
}
64+
},
65+
[onChange]
66+
);
67+
68+
return (
69+
<>
70+
<VStack align="stretch" spacing={2}>
71+
<FormControl isInvalid={isInvalid} isRequired={isRequired}>
72+
<FormLabel fontSize="sm" fontWeight="medium" color="gray.200">
73+
{label}
74+
</FormLabel>
75+
{as === "textarea" ? (
76+
<Textarea
77+
ref={inputRef as React.RefObject<HTMLTextAreaElement>}
78+
value={value}
79+
onChange={(e) => onChange(e.target.value)}
80+
placeholder={placeholder}
81+
rows={rows}
82+
bg="gray.700"
83+
color="white"
84+
fontFamily="mono"
85+
/>
86+
) : (
87+
<Input
88+
ref={inputRef as React.RefObject<HTMLInputElement>}
89+
value={value}
90+
onChange={(e) => onChange(e.target.value)}
91+
placeholder={placeholder}
92+
bg="gray.700"
93+
color="white"
94+
fontFamily="mono"
95+
/>
96+
)}
97+
<Button
98+
mt={2}
99+
leftIcon={<AddIcon />}
100+
size="sm"
101+
variant="outline"
102+
colorScheme="blue"
103+
onClick={() => setIsDialogOpen(true)}
104+
alignSelf="flex-start"
105+
aria-label={`Insert placeholder into ${label}`}
106+
>
107+
Insert Placeholder
108+
</Button>
109+
{helperText && (
110+
<FormHelperText fontSize="sm" color="gray.400">
111+
{helperText}
112+
</FormHelperText>
113+
)}
114+
{error && <FormErrorMessage>{error}</FormErrorMessage>}
115+
</FormControl>
116+
</VStack>
117+
<InsertPlaceholderDialog
118+
isOpen={isDialogOpen}
119+
onClose={() => setIsDialogOpen(false)}
120+
onSelected={handleInsertPlaceholder}
121+
onCloseFocusRef={inputRef}
122+
/>
123+
</>
124+
);
125+
};

services/backend-api/client/src/components/NavigableTree/index.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import {
22
ComponentProps,
3-
HTMLAttributes,
43
MouseEvent,
54
PropsWithChildren,
65
ReactElement,
7-
ReactNode,
86
useEffect,
97
useRef,
108
useState,
119
} from "react";
10+
import { chakra } from "@chakra-ui/react";
1211
import {
1312
NavigableTreeProvider,
1413
useNavigableTreeContext,
@@ -18,8 +17,6 @@ import {
1817
NavigableTreeItemProvider,
1918
useNavigableTreeItemContext,
2019
} from "../../contexts/NavigableTreeItemContext";
21-
import { chakra, SystemStyleObject } from "@chakra-ui/react";
22-
import getChakraColor from "../../utils/getChakraColor";
2320

2421
interface TreeProps extends PropsWithChildren {
2522
accessibleLabel: string;
@@ -37,13 +34,14 @@ const NavigableTree = ({ children, accessibleLabel }: TreeProps) => {
3734

3835
export const NavigableTreeItemGroup = (props: ComponentProps<typeof chakra.div>) => {
3936
const { isExpanded } = useNavigableTreeItemContext();
37+
4038
return (
4139
<chakra.div
4240
role="group"
43-
className={"navigable-tree-group"}
41+
className="navigable-tree-group"
4442
display={isExpanded ? "block" : "none"}
4543
{...props}
46-
></chakra.div>
44+
/>
4745
);
4846
};
4947

@@ -82,9 +80,9 @@ export const NavigableTreeItem = ({
8280
return;
8381
}
8482

85-
const hasGroup = treeItemRef.current.querySelector('.navigable-tree-group[role="group"]');
83+
const thisHasGroup = treeItemRef.current.querySelector('.navigable-tree-group[role="group"]');
8684

87-
setHasGroup(!!hasGroup);
85+
setHasGroup(!!thisHasGroup);
8886
}, [treeItemRef.current]);
8987

9088
return (
@@ -160,7 +158,12 @@ export const NavigableTreeItemExpandButton = <T,>({ children }: ButtonProps<T>)
160158
e.stopPropagation();
161159

162160
onFocused();
163-
isExpanded ? onCollapsed() : onExpanded();
161+
162+
if (isExpanded) {
163+
onCollapsed();
164+
} else {
165+
onExpanded();
166+
}
164167
},
165168
});
166169
};

0 commit comments

Comments
 (0)