aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd')
-rw-r--r--FrontEnd/src/pages/timeline/TimelineCard.tsx12
-rw-r--r--FrontEnd/src/pages/timeline/TimelineMember.css20
-rw-r--r--FrontEnd/src/pages/timeline/TimelineMember.tsx105
-rw-r--r--FrontEnd/src/views/common/SearchInput.tsx93
-rw-r--r--FrontEnd/src/views/common/list/ListContainer.css4
-rw-r--r--FrontEnd/src/views/common/list/ListContainer.tsx23
-rw-r--r--FrontEnd/src/views/common/list/ListItemContainer.css3
-rw-r--r--FrontEnd/src/views/common/list/ListItemContainer.tsx23
-rw-r--r--FrontEnd/src/views/common/list/index.ts4
9 files changed, 156 insertions, 131 deletions
diff --git a/FrontEnd/src/pages/timeline/TimelineCard.tsx b/FrontEnd/src/pages/timeline/TimelineCard.tsx
index d002a1b9..8557e5dd 100644
--- a/FrontEnd/src/pages/timeline/TimelineCard.tsx
+++ b/FrontEnd/src/pages/timeline/TimelineCard.tsx
@@ -8,7 +8,7 @@ import { HttpTimelineInfo } from "@/http/timeline";
import { getHttpBookmarkClient } from "@/http/bookmark";
import { useMobile } from "@/views/common/common";
-import { useDialog } from "@/views/common/dialog";
+import { Dialog, useDialog } from "@/views/common/dialog";
import UserAvatar from "@/views/common/user/UserAvatar";
import PopupMenu from "@/views/common/menu/PopupMenu";
import FullPageDialog from "@/views/common/dialog/FullPageDialog";
@@ -16,7 +16,7 @@ import Card from "@/views/common/Card";
import TimelineDeleteDialog from "./TimelineDeleteDialog";
import ConnectionStatusBadge from "./ConnectionStatusBadge";
import CollapseButton from "./CollapseButton";
-import { TimelineMemberDialog } from "./TimelineMember";
+import TimelineMember from "./TimelineMember";
import TimelinePropertyChangeDialog from "./TimelinePropertyChangeDialog";
import IconButton from "@/views/common/button/IconButton";
@@ -144,11 +144,9 @@ export default function TimelineCard(props: TimelinePageCardProps) {
) : (
<div style={{ display: collapse ? "none" : "block" }}>{content}</div>
)}
- <TimelineMemberDialog
- timeline={timeline}
- onChange={onReload}
- {...dialogPropsMap["member"]}
- />
+ <Dialog {...dialogPropsMap["member"]}>
+ <TimelineMember timeline={timeline} onChange={onReload} />
+ </Dialog>
<TimelinePropertyChangeDialog
timeline={timeline}
onChange={onReload}
diff --git a/FrontEnd/src/pages/timeline/TimelineMember.css b/FrontEnd/src/pages/timeline/TimelineMember.css
index adb78764..93fcffce 100644
--- a/FrontEnd/src/pages/timeline/TimelineMember.css
+++ b/FrontEnd/src/pages/timeline/TimelineMember.css
@@ -1,8 +1,20 @@
.timeline-member-item {
- border: var(--cru-background-1-color) solid;
- border-width: 0.5px 1px;
+ align-items: center;
+ display: flex;
+ padding: 0.5em;
}
-.timeline-member-item > div {
- padding: 0.5em;
+.timeline-member-avatar {
+ height: 50px;
+ width: 50px;
+ border-radius: 50%;
+}
+
+.timeline-member-info {
+ margin-left: 1em;
+ margin-right: auto;
+}
+
+.timeline-member-user-search {
+ margin-top: 1em;
}
diff --git a/FrontEnd/src/pages/timeline/TimelineMember.tsx b/FrontEnd/src/pages/timeline/TimelineMember.tsx
index 6c5d29ba..4c1600f5 100644
--- a/FrontEnd/src/pages/timeline/TimelineMember.tsx
+++ b/FrontEnd/src/pages/timeline/TimelineMember.tsx
@@ -1,5 +1,4 @@
import { useState } from "react";
-import * as React from "react";
import { useTranslation } from "react-i18next";
import { convertI18nText, I18nText } from "@/common";
@@ -11,45 +10,50 @@ import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline";
import SearchInput from "@/views/common/SearchInput";
import UserAvatar from "@/views/common/user/UserAvatar";
import Button from "@/views/common/button/Button";
-import Dialog from "@/views/common/dialog/Dialog";
+import { ListContainer, ListItemContainer } from "@/views/common/list";
import "./TimelineMember.css";
-const TimelineMemberItem: React.FC<{
+function TimelineMemberItem({
+ user,
+ add,
+ onAction,
+}: {
user: HttpUser;
add?: boolean;
onAction?: (username: string) => void;
-}> = ({ user, add, onAction }) => {
+}) {
return (
- <div className="container timeline-member-item">
- <div className="row">
- <div className="col col-auto">
- <UserAvatar username={user.username} className="cru-avatar small" />
- </div>
- <div className="col">
- <div className="row">{user.nickname}</div>
- <small className="row">{"@" + user.username}</small>
- </div>
- {onAction ? (
- <div className="col col-auto">
- <Button
- text={`timeline.member.${add ? "add" : "remove"}`}
- color={add ? "success" : "danger"}
- onClick={() => {
- onAction(user.username);
- }}
- />
- </div>
- ) : null}
+ <ListItemContainer className="timeline-member-item">
+ <UserAvatar username={user.username} className="timeline-member-avatar" />
+ <div className="timeline-member-info">
+ <div className="timeline-member-nickname">{user.nickname}</div>
+ <small className="timeline-member-username">
+ {"@" + user.username}
+ </small>
</div>
- </div>
+ {onAction ? (
+ <div className="timeline-member-action">
+ <Button
+ text={`timeline.member.${add ? "add" : "remove"}`}
+ color={add ? "create" : "danger"}
+ onClick={() => {
+ onAction(user.username);
+ }}
+ />
+ </div>
+ ) : null}
+ </ListItemContainer>
);
-};
+}
-const TimelineMemberUserSearch: React.FC<{
+function TimelineMemberUserSearch({
+ timeline,
+ onChange,
+}: {
timeline: HttpTimelineInfo;
onChange: () => void;
-}> = ({ timeline, onChange }) => {
+}) {
const { t } = useTranslation();
const [userSearchText, setUserSearchText] = useState<string>("");
@@ -64,9 +68,9 @@ const TimelineMemberUserSearch: React.FC<{
>({ type: "init" });
return (
- <>
+ <div className="timeline-member-user-search">
<SearchInput
- className="mt-3"
+ className=""
value={userSearchText}
onChange={(v) => {
setUserSearchText(v);
@@ -88,8 +92,8 @@ const TimelineMemberUserSearch: React.FC<{
users = users.filter(
(user) =>
timeline.members.findIndex(
- (m) => m.username === user.username
- ) === -1 && timeline.owner.username !== user.username
+ (m) => m.username === user.username,
+ ) === -1 && timeline.owner.username !== user.username,
);
setUserSearchState({ type: "users", data: users });
},
@@ -98,7 +102,7 @@ const TimelineMemberUserSearch: React.FC<{
type: "error",
data: { type: "custom", value: String(e) },
});
- }
+ },
);
}}
/>
@@ -109,7 +113,7 @@ const TimelineMemberUserSearch: React.FC<{
return <div>{t("timeline.member.noUserAvailableToAdd")}</div>;
} else {
return (
- <div className="mt-2">
+ <div className="">
{users.map((user) => (
<TimelineMemberItem
key={user.username}
@@ -120,7 +124,7 @@ const TimelineMemberUserSearch: React.FC<{
.memberPut(
timeline.owner.username,
timeline.nameV2,
- user.username
+ user.username,
)
.then(() => {
setUserSearchText("");
@@ -141,22 +145,22 @@ const TimelineMemberUserSearch: React.FC<{
);
}
})()}
- </>
+ </div>
);
-};
+}
-export interface TimelineMemberProps {
+interface TimelineMemberProps {
timeline: HttpTimelineInfo;
onChange: () => void;
}
-const TimelineMember: React.FC<TimelineMemberProps> = (props) => {
+export default function TimelineMember(props: TimelineMemberProps) {
const { timeline, onChange } = props;
const members = [timeline.owner, ...timeline.members];
return (
<div className="container px-4 py-3">
- <div>
+ <ListContainer>
{members.map((member, index) => (
<TimelineMemberItem
key={member.username}
@@ -168,7 +172,7 @@ const TimelineMember: React.FC<TimelineMemberProps> = (props) => {
.memberDelete(
timeline.owner.username,
timeline.nameV2,
- member.username
+ member.username,
)
.then(onChange);
}
@@ -176,27 +180,10 @@ const TimelineMember: React.FC<TimelineMemberProps> = (props) => {
}
/>
))}
- </div>
+ </ListContainer>
{timeline.manageable ? (
<TimelineMemberUserSearch timeline={timeline} onChange={onChange} />
) : null}
</div>
);
-};
-
-export default TimelineMember;
-
-export interface TimelineMemberDialogProps extends TimelineMemberProps {
- open: boolean;
- onClose: () => void;
}
-
-export const TimelineMemberDialog: React.FC<TimelineMemberDialogProps> = (
- props
-) => {
- return (
- <Dialog open={props.open} onClose={props.onClose}>
- <TimelineMember {...props} />
- </Dialog>
- );
-};
diff --git a/FrontEnd/src/views/common/SearchInput.tsx b/FrontEnd/src/views/common/SearchInput.tsx
index 9d644ab7..e3216b86 100644
--- a/FrontEnd/src/views/common/SearchInput.tsx
+++ b/FrontEnd/src/views/common/SearchInput.tsx
@@ -1,79 +1,50 @@
-import { useCallback } from "react";
-import * as React from "react";
-import classnames from "classnames";
-import { useTranslation } from "react-i18next";
+import classNames from "classnames";
+import { useC, Text } from "./common";
import LoadingButton from "./button/LoadingButton";
import "./SearchInput.css";
-export interface SearchInputProps {
+interface SearchInputProps {
value: string;
onChange: (value: string) => void;
onButtonClick: () => void;
- className?: string;
loading?: boolean;
- buttonText?: string;
- placeholder?: string;
- additionalButton?: React.ReactNode;
- alwaysOneline?: boolean;
+ className?: string;
+ buttonText?: Text;
}
-const SearchInput: React.FC<SearchInputProps> = (props) => {
- const { onChange, onButtonClick, alwaysOneline } = props;
-
- const { t } = useTranslation();
-
- const onInputChange = useCallback(
- (event: React.ChangeEvent<HTMLInputElement>): void => {
- onChange(event.currentTarget.value);
- },
- [onChange]
- );
-
- const onInputKeyPress = useCallback(
- (event: React.KeyboardEvent<HTMLInputElement>): void => {
- if (event.key === "Enter") {
- onButtonClick();
- event.preventDefault();
- }
- },
- [onButtonClick]
- );
+export default function SearchInput({
+ value,
+ onChange,
+ onButtonClick,
+ loading,
+ className,
+ buttonText,
+}: SearchInputProps) {
+ const c = useC();
return (
- <div
- className={classnames(
- "cru-search-input",
- alwaysOneline ? "flex-nowrap" : "flex-sm-nowrap",
- props.className
- )}
- >
+ <div className={classNames("cru-search-input", className)}>
<input
type="text"
- className="cru-search-input-input me-sm-2 flex-grow-1"
- value={props.value}
- onChange={onInputChange}
- onKeyPress={onInputKeyPress}
- placeholder={props.placeholder}
+ className="cru-search-input-input"
+ value={value}
+ onChange={(event) => {
+ const { value } = event.currentTarget;
+ onChange(value);
+ }}
+ onKeyDown={(event) => {
+ if (event.key === "Enter") {
+ onButtonClick();
+ event.preventDefault();
+ }
+ }}
/>
- {props.additionalButton ? (
- <div className="mt-2 mt-sm-0 flex-shrink-0 order-sm-last ms-sm-2">
- {props.additionalButton}
- </div>
- ) : null}
- <div
- className={classnames(
- alwaysOneline ? "mt-0 ms-2" : "mt-2 mt-sm-0 ms-auto ms-sm-0",
- "flex-shrink-0"
- )}
- >
- <LoadingButton loading={props.loading} onClick={props.onButtonClick}>
- {props.buttonText ?? t("search")}
- </LoadingButton>
- </div>
+
+ <LoadingButton loading={loading} onClick={onButtonClick}>
+ {c(buttonText ?? "search")}
+ </LoadingButton>
</div>
);
-};
-
-export default SearchInput;
+}
diff --git a/FrontEnd/src/views/common/list/ListContainer.css b/FrontEnd/src/views/common/list/ListContainer.css
new file mode 100644
index 00000000..679f139d
--- /dev/null
+++ b/FrontEnd/src/views/common/list/ListContainer.css
@@ -0,0 +1,4 @@
+.cru-list-container {
+ border: 1px solid var(--cru-button-primary-normal-color);
+ border-radius: 5px;
+}
diff --git a/FrontEnd/src/views/common/list/ListContainer.tsx b/FrontEnd/src/views/common/list/ListContainer.tsx
new file mode 100644
index 00000000..aa00d12c
--- /dev/null
+++ b/FrontEnd/src/views/common/list/ListContainer.tsx
@@ -0,0 +1,23 @@
+import { ComponentPropsWithoutRef, forwardRef, Ref } from "react";
+import classNames from "classnames";
+
+import "./ListContainer.css"
+
+function _ListContainer(
+ { className, children, ...otherProps }: ComponentPropsWithoutRef<"div">,
+ ref: Ref<HTMLDivElement>,
+) {
+ return (
+ <div
+ ref={ref}
+ className={classNames("cru-list-container", className)}
+ {...otherProps}
+ >
+ {children}
+ </div>
+ );
+}
+
+const ListContainer = forwardRef(_ListContainer);
+
+export default ListContainer;
diff --git a/FrontEnd/src/views/common/list/ListItemContainer.css b/FrontEnd/src/views/common/list/ListItemContainer.css
new file mode 100644
index 00000000..4c08a8d1
--- /dev/null
+++ b/FrontEnd/src/views/common/list/ListItemContainer.css
@@ -0,0 +1,3 @@
+.cru-list-item-container {
+ border: 1px solid var(--cru-button-primary-normal-color);
+}
diff --git a/FrontEnd/src/views/common/list/ListItemContainer.tsx b/FrontEnd/src/views/common/list/ListItemContainer.tsx
new file mode 100644
index 00000000..315cbd6e
--- /dev/null
+++ b/FrontEnd/src/views/common/list/ListItemContainer.tsx
@@ -0,0 +1,23 @@
+import { ComponentPropsWithoutRef, forwardRef, Ref } from "react";
+import classNames from "classnames";
+
+import "./ListItemContainer.css";
+
+function _ListItemContainer(
+ { className, children, ...otherProps }: ComponentPropsWithoutRef<"div">,
+ ref: Ref<HTMLDivElement>,
+) {
+ return (
+ <div
+ ref={ref}
+ className={classNames("cru-list-item-container", className)}
+ {...otherProps}
+ >
+ {children}
+ </div>
+ );
+}
+
+const ListItemContainer = forwardRef(_ListItemContainer);
+
+export default ListItemContainer;
diff --git a/FrontEnd/src/views/common/list/index.ts b/FrontEnd/src/views/common/list/index.ts
new file mode 100644
index 00000000..e183f7da
--- /dev/null
+++ b/FrontEnd/src/views/common/list/index.ts
@@ -0,0 +1,4 @@
+import ListContainer from "./ListContainer";
+import ListItemContainer from "./ListItemContainer";
+
+export { ListContainer, ListItemContainer };