diff options
Diffstat (limited to 'FrontEnd/src')
-rw-r--r-- | FrontEnd/src/pages/timeline/TimelineCard.tsx | 12 | ||||
-rw-r--r-- | FrontEnd/src/pages/timeline/TimelineMember.css | 20 | ||||
-rw-r--r-- | FrontEnd/src/pages/timeline/TimelineMember.tsx | 105 | ||||
-rw-r--r-- | FrontEnd/src/views/common/SearchInput.tsx | 93 | ||||
-rw-r--r-- | FrontEnd/src/views/common/list/ListContainer.css | 4 | ||||
-rw-r--r-- | FrontEnd/src/views/common/list/ListContainer.tsx | 23 | ||||
-rw-r--r-- | FrontEnd/src/views/common/list/ListItemContainer.css | 3 | ||||
-rw-r--r-- | FrontEnd/src/views/common/list/ListItemContainer.tsx | 23 | ||||
-rw-r--r-- | FrontEnd/src/views/common/list/index.ts | 4 |
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 }; |