diff options
-rw-r--r-- | FrontEnd/src/views/common/button/ButtonRow.css | 0 | ||||
-rw-r--r-- | FrontEnd/src/views/common/button/ButtonRow.tsx | 62 | ||||
-rw-r--r-- | FrontEnd/src/views/common/button/index.tsx | 3 | ||||
-rw-r--r-- | FrontEnd/src/views/common/dialog/ConfirmDialog.css | 0 | ||||
-rw-r--r-- | FrontEnd/src/views/common/dialog/ConfirmDialog.tsx | 80 | ||||
-rw-r--r-- | FrontEnd/src/views/common/dialog/Dialog.css | 9 | ||||
-rw-r--r-- | FrontEnd/src/views/common/dialog/DialogContainer.css | 20 | ||||
-rw-r--r-- | FrontEnd/src/views/common/dialog/DialogContainer.tsx | 68 | ||||
-rw-r--r-- | FrontEnd/src/views/common/dialog/OperationDialog.css | 15 | ||||
-rw-r--r-- | FrontEnd/src/views/common/dialog/OperationDialog.tsx | 94 |
10 files changed, 250 insertions, 101 deletions
diff --git a/FrontEnd/src/views/common/button/ButtonRow.css b/FrontEnd/src/views/common/button/ButtonRow.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/FrontEnd/src/views/common/button/ButtonRow.css diff --git a/FrontEnd/src/views/common/button/ButtonRow.tsx b/FrontEnd/src/views/common/button/ButtonRow.tsx new file mode 100644 index 00000000..eea60cc4 --- /dev/null +++ b/FrontEnd/src/views/common/button/ButtonRow.tsx @@ -0,0 +1,62 @@ +import { ComponentPropsWithoutRef, Ref } from "react"; +import classNames from "classnames"; + +import Button from "./Button"; +import FlatButton from "./FlatButton"; +import IconButton from "./IconButton"; +import LoadingButton from "./LoadingButton"; + +import "./ButtonRow.css"; + +type ButtonRowButton = ( + | { + type: "normal"; + props: ComponentPropsWithoutRef<typeof Button>; + } + | { + type: "flat"; + props: ComponentPropsWithoutRef<typeof FlatButton>; + } + | { + type: "icon"; + props: ComponentPropsWithoutRef<typeof IconButton>; + } + | { type: "loading"; props: ComponentPropsWithoutRef<typeof LoadingButton> } +) & { key: string | number }; + +interface ButtonRowProps { + className?: string; + containerRef?: Ref<HTMLDivElement>; + buttons: ButtonRowButton[]; + buttonsClassName?: string; +} + +export default function ButtonRow({ + className, + containerRef, + buttons, + buttonsClassName, +}: ButtonRowProps) { + return ( + <div ref={containerRef} className={classNames("cru-button-row", className)}> + {buttons.map((button) => { + const { type, key, props } = button; + const newClassName = classNames(props.className, buttonsClassName); + switch (type) { + case "normal": + return <Button key={key} {...props} className={newClassName} />; + case "flat": + return <FlatButton key={key} {...props} className={newClassName} />; + case "icon": + return <IconButton key={key} {...props} className={newClassName} />; + case "loading": + return ( + <LoadingButton key={key} {...props} className={newClassName} /> + ); + default: + throw new Error(); + } + })} + </div> + ); +} diff --git a/FrontEnd/src/views/common/button/index.tsx b/FrontEnd/src/views/common/button/index.tsx index cff5ba3f..73038849 100644 --- a/FrontEnd/src/views/common/button/index.tsx +++ b/FrontEnd/src/views/common/button/index.tsx @@ -2,5 +2,6 @@ import Button from "./Button"; import FlatButton from "./FlatButton"; import IconButton from "./IconButton"; import LoadingButton from "./LoadingButton"; +import ButtonRow from "./ButtonRow"; -export { Button, FlatButton, IconButton, LoadingButton }; +export { Button, FlatButton, IconButton, LoadingButton, ButtonRow }; diff --git a/FrontEnd/src/views/common/dialog/ConfirmDialog.css b/FrontEnd/src/views/common/dialog/ConfirmDialog.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/FrontEnd/src/views/common/dialog/ConfirmDialog.css diff --git a/FrontEnd/src/views/common/dialog/ConfirmDialog.tsx b/FrontEnd/src/views/common/dialog/ConfirmDialog.tsx index 8c2cea5a..ee4bf2d7 100644 --- a/FrontEnd/src/views/common/dialog/ConfirmDialog.tsx +++ b/FrontEnd/src/views/common/dialog/ConfirmDialog.tsx @@ -1,43 +1,59 @@ -import { convertI18nText, I18nText } from "@/common"; -import * as React from "react"; -import { useTranslation } from "react-i18next"; +import { useC, Text, ThemeColor } from "@/views/common/common"; -import Button from "../button/Button"; import Dialog from "./Dialog"; +import DialogContainer from "./DialogContainer"; -const ConfirmDialog: React.FC<{ +export default function ConfirmDialog({ + open, + onClose, + onConfirm, + title, + body, + color, + bodyColor, +}: { open: boolean; onClose: () => void; onConfirm: () => void; - title: I18nText; - body: I18nText; -}> = ({ open, onClose, onConfirm, title, body }) => { - const { t } = useTranslation(); + title: Text; + body: Text; + color?: ThemeColor; + bodyColor?: ThemeColor; +}) { + const c = useC(); return ( <Dialog onClose={onClose} open={open}> - <h3 className="cru-color-danger">{convertI18nText(title, t)}</h3> - <hr /> - <p>{convertI18nText(body, t)}</p> - <hr /> - <div className="cru-dialog-bottom-area"> - <Button - text="operationDialog.cancel" - color="secondary" - outline - onClick={onClose} - /> - <Button - text="operationDialog.confirm" - color="danger" - onClick={() => { - onConfirm(); - onClose(); - }} - /> - </div> + <DialogContainer + title={title} + titleColor={color} + buttons={[ + { + key: "cancel", + type: "normal", + props: { + text: "operationDialog.cancel", + color: "secondary", + outline: true, + onClick: onClose, + }, + }, + { + key: "confirm", + type: "normal", + props: { + text: "operationDialog.confirm", + color: "danger", + onClick: () => { + onConfirm(); + onClose(); + }, + }, + }, + ]} + > + <div className={`cru-${bodyColor ?? "primary"}`}>{c(body)}</div> + </DialogContainer> </Dialog> ); -}; - -export default ConfirmDialog; +} diff --git a/FrontEnd/src/views/common/dialog/Dialog.css b/FrontEnd/src/views/common/dialog/Dialog.css index 0123a29d..e4c61440 100644 --- a/FrontEnd/src/views/common/dialog/Dialog.css +++ b/FrontEnd/src/views/common/dialog/Dialog.css @@ -39,15 +39,6 @@ }
}
-.cru-dialog-bottom-area {
- display: flex;
- justify-content: flex-end;
-}
-
-.cru-dialog-bottom-area>* {
- margin: 0 0.5em;
-}
-
.cru-dialog-enter .cru-dialog-container {
transform: scale(0, 0);
opacity: 0;
diff --git a/FrontEnd/src/views/common/dialog/DialogContainer.css b/FrontEnd/src/views/common/dialog/DialogContainer.css new file mode 100644 index 00000000..fbb18e0d --- /dev/null +++ b/FrontEnd/src/views/common/dialog/DialogContainer.css @@ -0,0 +1,20 @@ +.cru-dialog-container-title { + font-size: 1.2em; + font-weight: bold; + color: var(--cru-key-color); + margin-bottom: 0.5em; +} + + +.cru-dialog-container-hr { + margin: 1em 0; +} + +.cru-dialog-container-button-row { + display: flex; + justify-content: flex-end; +} + +.cru-dialog-container-button { + margin-left: 1em; +}
\ No newline at end of file diff --git a/FrontEnd/src/views/common/dialog/DialogContainer.tsx b/FrontEnd/src/views/common/dialog/DialogContainer.tsx new file mode 100644 index 00000000..b0a87ea5 --- /dev/null +++ b/FrontEnd/src/views/common/dialog/DialogContainer.tsx @@ -0,0 +1,68 @@ +import { ComponentProps, Ref, ReactNode } from "react"; +import classNames from "classnames"; + +import { ThemeColor, Text, useC } from "../common"; +import { ButtonRow } from "../button"; + +import "./DialogContainer.css"; + +interface DialogContainerProps { + className?: string; + title: Text; + titleColor?: ThemeColor; + titleClassName?: string; + titleRef?: Ref<HTMLDivElement>; + bodyContainerClassName?: string; + bodyContainerRef?: Ref<HTMLDivElement>; + buttons: ComponentProps<typeof ButtonRow>["buttons"]; + buttonsClassName?: string; + buttonsContainerRef?: ComponentProps<typeof ButtonRow>["containerRef"]; + children: ReactNode; +} + +export default function DialogContainer({ + className, + title, + titleColor, + titleClassName, + titleRef, + bodyContainerClassName, + bodyContainerRef, + buttons, + buttonsClassName, + buttonsContainerRef, + children, +}: DialogContainerProps) { + const c = useC(); + + return ( + <div className={classNames(className)}> + <div + ref={titleRef} + className={classNames( + `cru-dialog-container-title cru-${titleColor ?? "primary"}`, + titleClassName, + )} + > + {c(title)} + </div> + <hr className="cru-dialog-container-hr" /> + <div + ref={bodyContainerRef} + className={classNames( + "cru-dialog-container-body", + bodyContainerClassName, + )} + > + {children} + </div> + <hr className="cru-dialog-container-hr" /> + <ButtonRow + containerRef={buttonsContainerRef} + className={classNames("cru-dialog-container-button-row", buttonsClassName)} + buttons={buttons} + buttonsClassName="cru-dialog-container-button" + /> + </div> + ); +} diff --git a/FrontEnd/src/views/common/dialog/OperationDialog.css b/FrontEnd/src/views/common/dialog/OperationDialog.css index 43cdb692..f4b7237e 100644 --- a/FrontEnd/src/views/common/dialog/OperationDialog.css +++ b/FrontEnd/src/views/common/dialog/OperationDialog.css @@ -1,22 +1,7 @@ -.cru-operation-dialog-title {
- font-size: 1.2em;
- font-weight: bold;
- color: var(--cru-key-color);
- margin-bottom: 0.5em;
-}
-
.cru-operation-dialog-prompt {
color: var(--cru-surface-on-color);
}
-.cru-dialog-middle-area {
- margin: 0.5em 0;
-}
-
-.cru-dialog-bottom-area {
- margin-top: 0.5em;
-}
-
.cru-operation-dialog-input-group {
display: block;
margin: 0.5em 0;
diff --git a/FrontEnd/src/views/common/dialog/OperationDialog.tsx b/FrontEnd/src/views/common/dialog/OperationDialog.tsx index 74b4a5fa..da1ff0e0 100644 --- a/FrontEnd/src/views/common/dialog/OperationDialog.tsx +++ b/FrontEnd/src/views/common/dialog/OperationDialog.tsx @@ -1,9 +1,8 @@ -import { useState, ReactNode } from "react"; +import { useState, ReactNode, ComponentProps } from "react"; import classNames from "classnames"; import { useC, Text, ThemeColor } from "../common"; -import Button from "../button/Button"; import { useInputs, InputGroup, @@ -11,8 +10,8 @@ import { InputValueDict, InputErrorDict, } from "../input/InputGroup"; -import LoadingButton from "../button/LoadingButton"; import Dialog from "./Dialog"; +import DialogContainer from "./DialogContainer"; import "./OperationDialog.css"; @@ -124,39 +123,45 @@ function OperationDialog<TData>(props: OperationDialogProps<TData>) { } let body: ReactNode; + let buttons: ComponentProps<typeof DialogContainer>["buttons"]; + if (step.type === "input" || step.type === "process") { const isProcessing = step.type === "process"; body = ( - <div className="cru-operation-dialog-main-area"> - <div className="cru-dialog-middle-area"> - <OperationDialogPrompt customMessage={c(inputPrompt)} /> - <InputGroup - containerClassName="cru-operation-dialog-input-group" - color={inputColor ?? "primary"} - {...inputGroupProps} - /> - </div> - <hr /> - <div className="cru-dialog-bottom-area"> - <Button - text="operationDialog.cancel" - color="secondary" - outline - onClick={close} - disabled={isProcessing} - /> - <LoadingButton - color={color} - loading={isProcessing} - disabled={hasErrorAndDirty} - onClick={onConfirm} - > - {c("operationDialog.confirm")} - </LoadingButton> - </div> + <div> + <OperationDialogPrompt customMessage={c(inputPrompt)} /> + <InputGroup + containerClassName="cru-operation-dialog-input-group" + color={inputColor ?? "primary"} + {...inputGroupProps} + /> </div> ); + buttons = [ + { + key: "cancel", + type: "normal", + props: { + text: "operationDialog.cancel", + color: "secondary", + outline: true, + onClick: close, + disabled: isProcessing, + }, + }, + { + key: "confirm", + type: "loading", + props: { + text: "operationDialog.confirm", + color, + loading: isProcessing, + disabled: hasErrorAndDirty, + onClick: onConfirm, + }, + }, + ]; } else { const result = step; @@ -171,28 +176,29 @@ function OperationDialog<TData>(props: OperationDialogProps<TData>) { customMessage: failurePrompt?.(result.data), }; body = ( - <div className="cru-operation-dialog-main-area"> + <div> <OperationDialogPrompt {...promptProps} /> - <hr /> - <div className="cru-dialog-bottom-area"> - <Button text="operationDialog.ok" color="primary" onClick={close} /> - </div> </div> ); + + buttons = [ + { + key: "ok", + type: "normal", + props: { + text: "operationDialog.ok", + color: "primary", + onClick: close, + }, + }, + ]; } return ( <Dialog open={open} onClose={close}> - <div - className={classNames( - "cru-operation-dialog-container", - `cru-${color ?? "primary"}`, - )} - > - <div className="cru-operation-dialog-title">{c(title)}</div> - <hr /> + <DialogContainer title={title} titleColor={color} buttons={buttons}> {body} - </div> + </DialogContainer> </Dialog> ); } |