diff options
Diffstat (limited to 'FrontEnd/src')
-rw-r--r-- | FrontEnd/src/pages/timeline/TimelineCard.css | 47 | ||||
-rw-r--r-- | FrontEnd/src/pages/timeline/TimelineCard.tsx | 94 | ||||
-rw-r--r-- | FrontEnd/src/views/common/Icon.css | 3 | ||||
-rw-r--r-- | FrontEnd/src/views/common/Icon.tsx | 29 | ||||
-rw-r--r-- | FrontEnd/src/views/common/menu/Menu.css | 7 | ||||
-rw-r--r-- | FrontEnd/src/views/common/menu/Menu.tsx | 49 | ||||
-rw-r--r-- | FrontEnd/src/views/common/menu/PopupMenu.css | 2 | ||||
-rw-r--r-- | FrontEnd/src/views/common/menu/PopupMenu.tsx | 82 |
8 files changed, 188 insertions, 125 deletions
diff --git a/FrontEnd/src/pages/timeline/TimelineCard.css b/FrontEnd/src/pages/timeline/TimelineCard.css index 75ce6c51..1bffe980 100644 --- a/FrontEnd/src/pages/timeline/TimelineCard.css +++ b/FrontEnd/src/pages/timeline/TimelineCard.css @@ -6,13 +6,56 @@ margin: 0.5em; } +@media (min-width: 576px) { + .timeline-card-expand { + min-width: 400px; + } +} + .timeline-card-title { display: inline-block; vertical-align: middle; - color: var(--cru-key-on-color); + color: var(--cru-key-container-on-color); + margin: 0.5em 1em; } .timeline-card-title-name { margin-inline-start: 1em; - color: var(--cru-surface-on-color); + color: var(--cru-surface-on-variant-color); +} + +.timeline-card-user { + display: flex; + align-items: center; + margin: 0 1em 0.5em; +} + +.timeline-card-user-avatar { + width: 2em; + height: 2em; + border-radius: 50%; +} + +.timeline-card-user-nickname { + margin-inline: 0.6em; +} + +.timeline-card-description { + margin: 0 1em 0.5em; +} + +.timeline-card-top-right-area { + float: right; + display: flex; + align-items: center; + margin: 0 1em; +} + +.timeline-card-buttons { + display: flex; + justify-content: end; +} + +.timeline-card-button { + margin: 0 0.2em; }
\ No newline at end of file diff --git a/FrontEnd/src/pages/timeline/TimelineCard.tsx b/FrontEnd/src/pages/timeline/TimelineCard.tsx index bcdfa4c2..cdc8a5a7 100644 --- a/FrontEnd/src/pages/timeline/TimelineCard.tsx +++ b/FrontEnd/src/pages/timeline/TimelineCard.tsx @@ -1,15 +1,13 @@ import { useState } from "react"; -import classnames from "classnames"; import { HubConnectionState } from "@microsoft/signalr"; -import { useIsSmallScreen } from "@/utilities/hooks"; -import { timelineVisibilityTooltipTranslationMap } from "@/services/timeline"; import { useUser } from "@/services/user"; import { pushAlert } from "@/services/alert"; + import { HttpTimelineInfo } from "@/http/timeline"; import { getHttpBookmarkClient } from "@/http/bookmark"; -import { useC } from "@/views/common/common"; +import { useMobile } from "@/views/common/common"; import { useDialog } from "@/views/common/dialog"; import UserAvatar from "@/views/common/user/UserAvatar"; import PopupMenu from "@/views/common/menu/PopupMenu"; @@ -35,17 +33,18 @@ export default function TimelineCard(props: TimelinePageCardProps) { const user = useUser(); - const c = useC(); - const [collapse, setCollapse] = useState(true); const toggleCollapse = (): void => { setCollapse((o) => !o); }; - const isSmallScreen = useIsSmallScreen(); + const isMobile = useMobile(); - const { createDialogSwitch, dialog, dialogPropsMap, switchDialog } = - useDialog(["member", "property", "delete"]); + const { createDialogSwitch, dialogPropsMap } = useDialog([ + "member", + "property", + "delete", + ]); const content = ( <div className="cru-primary"> @@ -53,25 +52,24 @@ export default function TimelineCard(props: TimelinePageCardProps) { {timeline.title} <small className="timeline-card-title-name">{timeline.nameV2}</small> </h3> - <div> + <div className="timeline-card-user"> <UserAvatar username={timeline.owner.username} - className="cru-avatar small cru-round me-3" + className="timeline-card-user-avatar" /> - {timeline.owner.nickname} - <small className="ms-3 cru-color-secondary"> + <span className="timeline-card-user-nickname"> + {timeline.owner.nickname} + </span> + <small className="timeline-card-user-username"> @{timeline.owner.username} </small> </div> - <p className="mb-0">{timeline.description}</p> - <small className="mt-1 d-block"> - {c(timelineVisibilityTooltipTranslationMap[timeline.visibility])} - </small> - <div className="mt-2 cru-text-end"> - {user != null ? ( + <p className="timeline-card-description">{timeline.description}</p> + <div className="timeline-card-buttons"> + {user && ( <IconButton icon={timeline.isBookmark ? "bookmark-fill" : "bookmark"} - className="me-3" + className="timeline-card-button" onClick={() => { getHttpBookmarkClient() [timeline.isBookmark ? "delete" : "post"]( @@ -89,13 +87,13 @@ export default function TimelineCard(props: TimelinePageCardProps) { }); }} /> - ) : null} + )} <IconButton icon="people" - className="me-3" + className="timeline-card-button" onClick={createDialogSwitch("member")} /> - {timeline.manageable ? ( + {timeline.manageable && ( <PopupMenu items={[ { @@ -113,37 +111,37 @@ export default function TimelineCard(props: TimelinePageCardProps) { ]} containerClassName="d-inline" > - <IconButton icon="three-dots-vertical" /> + <IconButton + className="timeline-card-button" + icon="three-dots-vertical" + /> </PopupMenu> - ) : null} + )} </div> </div> ); return ( - <> - <Card className="timeline-card"> - <div - className={classnames( - "cru-float-right d-flex align-items-center", - !collapse && "ms-3", - )} + <Card + className={`timeline-card timeline-card-${ + collapse ? "collapse" : "expand" + }`} + > + <div className="timeline-card-top-right-area"> + <ConnectionStatusBadge status={connectionStatus} /> + <CollapseButton collapse={collapse} onClick={toggleCollapse} /> + </div> + {isMobile ? ( + <FullPageDialog + onBack={toggleCollapse} + show={!collapse} + contentContainerClassName="p-2" > - <ConnectionStatusBadge status={connectionStatus} className="me-2" /> - <CollapseButton collapse={collapse} onClick={toggleCollapse} /> - </div> - {isSmallScreen ? ( - <FullPageDialog - onBack={toggleCollapse} - show={!collapse} - contentContainerClassName="p-2" - > - {content} - </FullPageDialog> - ) : ( - <div style={{ display: collapse ? "none" : "inline" }}>{content}</div> - )} - </Card> + {content} + </FullPageDialog> + ) : ( + <div style={{ display: collapse ? "none" : "block" }}>{content}</div> + )} <TimelineMemberDialog timeline={timeline} onChange={onReload} @@ -155,6 +153,6 @@ export default function TimelineCard(props: TimelinePageCardProps) { {...dialogPropsMap["property"]} /> <TimelineDeleteDialog timeline={timeline} {...dialogPropsMap["delete"]} /> - </> + </Card> ); } diff --git a/FrontEnd/src/views/common/Icon.css b/FrontEnd/src/views/common/Icon.css new file mode 100644 index 00000000..fe980d7b --- /dev/null +++ b/FrontEnd/src/views/common/Icon.css @@ -0,0 +1,3 @@ +.cru-icon { + font-size: 1.4rem; +} diff --git a/FrontEnd/src/views/common/Icon.tsx b/FrontEnd/src/views/common/Icon.tsx new file mode 100644 index 00000000..c91d514b --- /dev/null +++ b/FrontEnd/src/views/common/Icon.tsx @@ -0,0 +1,29 @@ +import { ComponentPropsWithoutRef } from "react"; +import classNames from "classnames"; + +import { ThemeColor } from "./common"; + +import "./Icon.css"; + +interface IconButtonProps extends ComponentPropsWithoutRef<"i"> { + icon: string; + color?: ThemeColor | "on-surface"; + size?: string | number; +} + +export default function Icon(props: IconButtonProps) { + const { icon, color, size, style, className, ...otherProps } = props; + + const colorName = color === "on-surface" ? "surface-on" : color ?? "primary"; + + return ( + <i + style={size != null ? { ...style, fontSize: size } : style} + className={classNames( + `bi-${icon} cru-${colorName}-color cru-icon`, + className, + )} + {...otherProps} + /> + ); +} diff --git a/FrontEnd/src/views/common/menu/Menu.css b/FrontEnd/src/views/common/menu/Menu.css index 93c229f0..db0b7432 100644 --- a/FrontEnd/src/views/common/menu/Menu.css +++ b/FrontEnd/src/views/common/menu/Menu.css @@ -11,7 +11,7 @@ }
.cru-menu-item:hover {
- color: var(--cru-key-t-color);
+ color: var(--cru-key-on-color);
background-color: var(--cru-key-color);
}
@@ -20,5 +20,6 @@ }
.cru-menu-divider {
- border-top: 1px solid #e9ecef;
-}
+ border-width: 0;
+ border-top: 1px solid var(--cru-key-color);
+}
\ No newline at end of file diff --git a/FrontEnd/src/views/common/menu/Menu.tsx b/FrontEnd/src/views/common/menu/Menu.tsx index de3b1664..a9ebfedf 100644 --- a/FrontEnd/src/views/common/menu/Menu.tsx +++ b/FrontEnd/src/views/common/menu/Menu.tsx @@ -1,11 +1,10 @@ -import * as React from "react"; -import classnames from "classnames"; -import { useTranslation } from "react-i18next"; +import { CSSProperties } from "react"; +import classNames from "classnames"; -import { convertI18nText, I18nText } from "@/common"; -import { PaletteColorType } from "@/palette"; +import { useC, Text, ThemeColor } from "../common"; import "./Menu.css"; +import Icon from "../Icon"; export type MenuItem = | { @@ -13,9 +12,9 @@ export type MenuItem = } | { type: "button"; - text: I18nText; - iconClassName?: string; - color?: PaletteColorType; + text: Text; + icon?: string; + color?: ThemeColor; onClick: () => void; }; @@ -25,44 +24,38 @@ export type MenuProps = { items: MenuItems; onItemClicked?: () => void; className?: string; - style?: React.CSSProperties; + style?: CSSProperties; }; -export default function _Menu({ +export default function Menu({ items, onItemClicked, className, style, -}: MenuProps): React.ReactElement | null { - const { t } = useTranslation(); +}: MenuProps) { + const c = useC(); return ( - <div className={classnames("cru-menu", className)} style={style}> + <div + className={classNames("cru-menu cru-primary", className)} + style={style} + > {items.map((item, index) => { if (item.type === "divider") { - return <div key={index} className="cru-menu-divider" />; + return <hr key={index} className="cru-menu-divider" />; } else { + const { text, color, icon, onClick } = item; return ( <div key={index} - className={classnames( - "cru-menu-item", - `cru-${item.color ?? "primary"}` - )} + className={`cru-menu-item cru-${color ?? "primary"}`} onClick={() => { - item.onClick(); + onClick(); onItemClicked?.(); }} > - {item.iconClassName != null ? ( - <i - className={classnames( - item.iconClassName, - "cru-menu-item-icon" - )} - /> - ) : null} - {convertI18nText(item.text, t)} + {icon != null && <Icon color={color} icon={icon} />} + {c(text)} </div> ); } diff --git a/FrontEnd/src/views/common/menu/PopupMenu.css b/FrontEnd/src/views/common/menu/PopupMenu.css index f6654f68..38171ffd 100644 --- a/FrontEnd/src/views/common/menu/PopupMenu.css +++ b/FrontEnd/src/views/common/menu/PopupMenu.css @@ -2,5 +2,5 @@ z-index: 1040;
border-radius: 5px;
border: var(--cru-primary-color) 1px solid;
- background-color: white;
+ background-color: var(--cru-surface-color);
}
diff --git a/FrontEnd/src/views/common/menu/PopupMenu.tsx b/FrontEnd/src/views/common/menu/PopupMenu.tsx index 74ca7aba..7ac12755 100644 --- a/FrontEnd/src/views/common/menu/PopupMenu.tsx +++ b/FrontEnd/src/views/common/menu/PopupMenu.tsx @@ -1,5 +1,5 @@ +import { useState, CSSProperties, ReactNode } from "react"; import classNames from "classnames"; -import * as React from "react"; import { createPortal } from "react-dom"; import { usePopper } from "react-popper"; @@ -11,61 +11,57 @@ import "./PopupMenu.css"; export interface PopupMenuProps { items: MenuItems; - children?: React.ReactNode; + children?: ReactNode; containerClassName?: string; - containerStyle?: React.CSSProperties; + containerStyle?: CSSProperties; } -const PopupMenu: React.FC<PopupMenuProps> = ({ +export default function PopupMenu({ items, children, containerClassName, containerStyle, -}) => { - const [show, setShow] = React.useState<boolean>(false); +}: PopupMenuProps) { + const [show, setShow] = useState<boolean>(false); const [referenceElement, setReferenceElement] = - React.useState<HTMLDivElement | null>(null); - const [popperElement, setPopperElement] = - React.useState<HTMLDivElement | null>(null); + useState<HTMLDivElement | null>(null); + const [popperElement, setPopperElement] = useState<HTMLDivElement | null>( + null, + ); const { styles, attributes } = usePopper(referenceElement, popperElement); useClickOutside(popperElement, () => setShow(false), true); return ( - <> - <div - ref={setReferenceElement} - className={classNames( - "cru-popup-menu-trigger-container", - containerClassName + <div + ref={setReferenceElement} + className={classNames( + "cru-popup-menu-trigger-container", + containerClassName, + )} + style={containerStyle} + onClick={() => setShow(true)} + > + {children} + {show && + createPortal( + <div + ref={setPopperElement} + className="cru-popup-menu-menu-container" + style={styles.popper} + {...attributes.popper} + > + <Menu + items={items} + onItemClicked={() => { + setShow(false); + }} + /> + </div>, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + document.getElementById("portal")!, )} - style={containerStyle} - onClick={() => setShow(true)} - > - {children} - </div> - {show - ? createPortal( - <div - ref={setPopperElement} - className="cru-popup-menu-menu-container" - style={styles.popper} - {...attributes.popper} - > - <Menu - items={items} - onItemClicked={() => { - setShow(false); - }} - /> - </div>, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - document.getElementById("portal")! - ) - : null} - </> + </div> ); -}; - -export default PopupMenu; +} |