diff --git a/config b/config
index 36f81ff28..af2375238 160000
--- a/config
+++ b/config
@@ -1 +1 @@
-Subproject commit 36f81ff2864e6c3a7ef5af1fd752fab31e9b4edc
+Subproject commit af23752382cdb750ff865dedcd9d8a238f1b724d
diff --git a/frontend/src/Cabinet/assets/images/moonIcon.svg b/frontend/src/Cabinet/assets/images/moonIcon.svg
new file mode 100644
index 000000000..3c8a7af6d
--- /dev/null
+++ b/frontend/src/Cabinet/assets/images/moonIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/Cabinet/assets/images/sunIcon.svg b/frontend/src/Cabinet/assets/images/sunIcon.svg
new file mode 100644
index 000000000..c3a482e73
--- /dev/null
+++ b/frontend/src/Cabinet/assets/images/sunIcon.svg
@@ -0,0 +1,4 @@
+
diff --git a/frontend/src/Cabinet/components/AdminInfo/Chart/CoinUseLineChart.tsx b/frontend/src/Cabinet/components/AdminInfo/Chart/CoinUseLineChart.tsx
index 4860da99c..b4a4f9555 100644
--- a/frontend/src/Cabinet/components/AdminInfo/Chart/CoinUseLineChart.tsx
+++ b/frontend/src/Cabinet/components/AdminInfo/Chart/CoinUseLineChart.tsx
@@ -15,6 +15,7 @@ const CoinUseLineChart = ({
if (totalCoinUseData === undefined) {
return null;
}
+
const formattedData = [
{
id: "issuedCoin",
@@ -39,7 +40,7 @@ const CoinUseLineChart = ({
(data) => data.id === coinToggleType
);
- const yMin = Math.min(...filteredData[0].data.map((d) => d.y));
+ const yMin = 0;
const yMax = Math.max(...filteredData[0].data.map((d) => d.y));
// NOTE : y축 scale을 log로 표현하기 위해 Y scale을 설정
diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx b/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx
index 472e4251f..cd54e2466 100644
--- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx
+++ b/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container.tsx
@@ -1,86 +1,51 @@
-import { useEffect, useState } from "react";
+import { useEffect } from "react";
+import { useRecoilValue } from "recoil";
+import { displayStyleState } from "@/Cabinet/recoil/atoms";
import DisplayStyleCard from "@/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard";
import {
DisplayStyleToggleType,
DisplayStyleType,
} from "@/Cabinet/types/enum/displayStyle.type.enum";
+import useDisplayStyleToggle from "@/Cabinet/hooks/useDisplayStyleToggle";
+import {
+ isDeviceDarkMode,
+ updateBodyDisplayStyle,
+} from "@/Cabinet/utils/displayStyleUtils";
// 로컬스토리지의 display-style-toggle 값에 따라 DisplayStyleType 반환
export const getInitialDisplayStyle = (
- savedDisplayStyleToggle: DisplayStyleToggleType,
+ displayStyleToggle: DisplayStyleToggleType,
darkModeQuery: MediaQueryList
-) => {
+): DisplayStyleType => {
// 라이트 / 다크 버튼
- if (savedDisplayStyleToggle === DisplayStyleToggleType.LIGHT)
+ if (displayStyleToggle === DisplayStyleToggleType.LIGHT) {
return DisplayStyleType.LIGHT;
- else if (savedDisplayStyleToggle === DisplayStyleToggleType.DARK)
- return DisplayStyleType.DARK;
- // 디바이스 버튼
- if (darkModeQuery.matches) {
+ } else if (displayStyleToggle === DisplayStyleToggleType.DARK) {
return DisplayStyleType.DARK;
+ // 디바이스 버튼
+ } else {
+ return darkModeQuery.matches
+ ? DisplayStyleType.DARK
+ : DisplayStyleType.LIGHT;
}
- return DisplayStyleType.LIGHT;
};
const DisplayStyleCardContainer = () => {
- const savedDisplayStyleToggle =
- (localStorage.getItem("display-style-toggle") as DisplayStyleToggleType) ||
- DisplayStyleToggleType.DEVICE;
- var darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)");
- const initialDisplayStyle = getInitialDisplayStyle(
- savedDisplayStyleToggle,
- darkModeQuery
- );
- const [darkMode, setDarkMode] = useState(
- initialDisplayStyle as DisplayStyleType
- );
- const [toggleType, setToggleType] = useState(
- savedDisplayStyleToggle
- );
-
- const setColorsAndLocalStorage = (toggleType: DisplayStyleToggleType) => {
- setToggleType(toggleType);
- localStorage.setItem("display-style-toggle", toggleType);
- };
-
- const handleDisplayStyleButtonClick = (displayStyleToggleType: string) => {
- if (toggleType === displayStyleToggleType) return;
- setToggleType(
- displayStyleToggleType as React.SetStateAction
- );
- setColorsAndLocalStorage(displayStyleToggleType as DisplayStyleToggleType);
- };
+ const darkModeQuery = isDeviceDarkMode();
+ const toggleType = useRecoilValue(displayStyleState);
+ const { addDarkModeListener } = useDisplayStyleToggle();
useEffect(() => {
- darkModeQuery.addEventListener("change", (event) =>
- setDarkMode(
- event.matches ? DisplayStyleType.DARK : DisplayStyleType.LIGHT
- )
- );
- }, []);
+ const applyDisplayStyle = () => {
+ const newDarkMode = getInitialDisplayStyle(toggleType, darkModeQuery);
+ updateBodyDisplayStyle(newDarkMode);
+ };
- useEffect(() => {
- document.body.setAttribute("display-style", darkMode);
- }, [darkMode]);
-
- useEffect(() => {
- if (toggleType === DisplayStyleToggleType.LIGHT) {
- setDarkMode(DisplayStyleType.LIGHT);
- } else if (toggleType === DisplayStyleToggleType.DARK) {
- setDarkMode(DisplayStyleType.DARK);
- } else {
- setDarkMode(
- darkModeQuery.matches ? DisplayStyleType.DARK : DisplayStyleType.LIGHT
- );
- }
+ applyDisplayStyle();
+ addDarkModeListener(darkModeQuery, applyDisplayStyle);
}, [toggleType]);
- return (
-
- );
+ return ;
};
export default DisplayStyleCardContainer;
diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx b/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx
index 59685d886..0c3d1d1f9 100644
--- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx
+++ b/frontend/src/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.tsx
@@ -1,19 +1,18 @@
+import React from "react";
+import { useRecoilState } from "recoil";
import styled from "styled-components";
+import { displayStyleState } from "@/Cabinet/recoil/atoms";
import Card from "@/Cabinet/components/Card/Card";
import { CardContentWrapper } from "@/Cabinet/components/Card/CardStyles";
import { ReactComponent as MonitorMobileIcon } from "@/Cabinet/assets/images/monitorMobile.svg";
import { ReactComponent as MoonIcon } from "@/Cabinet/assets/images/moon.svg";
import { ReactComponent as SunIcon } from "@/Cabinet/assets/images/sun.svg";
import { DisplayStyleToggleType } from "@/Cabinet/types/enum/displayStyle.type.enum";
-
-interface DisplayStyleProps {
- displayStyleToggle: DisplayStyleToggleType;
- handleDisplayStyleButtonClick: (DisplayStyleToggleType: string) => void;
-}
+import useDisplayStyleToggle from "@/Cabinet/hooks/useDisplayStyleToggle";
interface IToggleItemSeparated {
name: string;
- key: string;
+ key: DisplayStyleToggleType;
icon: React.ComponentType>;
}
@@ -35,42 +34,48 @@ const toggleList: IToggleItemSeparated[] = [
},
];
-const DisplayStyleCard = ({
- displayStyleToggle,
- handleDisplayStyleButtonClick,
-}: DisplayStyleProps) => {
+export const updateLocalStorageDisplayStyleToggle = (
+ toggleType: DisplayStyleToggleType
+) => {
+ localStorage.setItem("display-style-toggle", toggleType);
+};
+
+const DisplayStyleCard = () => {
+ const [toggleType, setToggleType] = useRecoilState(displayStyleState);
+ const { updateToggleType } = useDisplayStyleToggle();
+ const handleButtonClick = (key: DisplayStyleToggleType) => {
+ if (toggleType === key) return;
+ updateToggleType(key);
+ };
+
return (
- <>
-
-
- <>
-
-
- {toggleList.map((item) => {
- const DisplayStyleIcon = item.icon;
- return (
- handleDisplayStyleButtonClick(item.key)}
- >
- {DisplayStyleIcon && }
- {item.name}
-
- );
- })}
-
-
- >
-
-
- >
+
+
+
+
+ {toggleList.map((item) => {
+ const DisplayStyleIcon = item.icon;
+ return (
+ handleButtonClick(item.key)}
+ >
+
+ {item.name}
+
+ );
+ })}
+
+
+
+
);
};
diff --git a/frontend/src/Cabinet/components/Card/DisplayStyleCard/displayStyleInitializer.ts b/frontend/src/Cabinet/components/Card/DisplayStyleCard/displayStyleInitializer.ts
index 3302a8b48..d4e4b4112 100644
--- a/frontend/src/Cabinet/components/Card/DisplayStyleCard/displayStyleInitializer.ts
+++ b/frontend/src/Cabinet/components/Card/DisplayStyleCard/displayStyleInitializer.ts
@@ -3,19 +3,19 @@ import {
DisplayStyleToggleType,
DisplayStyleType,
} from "@/Cabinet/types/enum/displayStyle.type.enum";
+import {
+ getDisplayStyleFromLocalStorage,
+ isDeviceDarkMode,
+ updateBodyDisplayStyle,
+} from "@/Cabinet/utils/displayStyleUtils";
(function () {
const isClient = typeof window !== "undefined";
if (isClient) {
- const savedDisplayStyleToggle =
- (localStorage.getItem(
- "display-style-toggle"
- ) as DisplayStyleToggleType) || DisplayStyleToggleType.DEVICE;
-
- const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)");
+ const darkModeQuery = isDeviceDarkMode();
const colorMode = getInitialDisplayStyle(
- savedDisplayStyleToggle,
+ getDisplayStyleFromLocalStorage(),
darkModeQuery
);
@@ -27,7 +27,7 @@ import {
// 이 코드가 실행중일땐 전역변수가 아직 정의가 안된 상태라 전역변수 대신 hex code 사용
document.addEventListener("DOMContentLoaded", function () {
- document.body.setAttribute("display-style", colorMode);
+ updateBodyDisplayStyle(colorMode);
});
}
})();
diff --git a/frontend/src/Cabinet/components/Common/DarkModeToggleSwitch.tsx b/frontend/src/Cabinet/components/Common/DarkModeToggleSwitch.tsx
new file mode 100644
index 000000000..5eb8f52c5
--- /dev/null
+++ b/frontend/src/Cabinet/components/Common/DarkModeToggleSwitch.tsx
@@ -0,0 +1,158 @@
+import { useCallback, useEffect, useState } from "react";
+import { useRecoilState } from "recoil";
+import styled from "styled-components";
+import { displayStyleState } from "@/Cabinet/recoil/atoms";
+import { getInitialDisplayStyle } from "@/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard.container";
+import { ReactComponent as MoonIcon } from "@/Cabinet/assets/images/moonIcon.svg";
+import { ReactComponent as SunIcon } from "@/Cabinet/assets/images/sunIcon.svg";
+import {
+ DisplayStyleToggleType,
+ DisplayStyleType,
+} from "@/Cabinet/types/enum/displayStyle.type.enum";
+import useDisplayStyleToggle from "@/Cabinet/hooks/useDisplayStyleToggle";
+import {
+ getDisplayStyleFromLocalStorage,
+ isDeviceDarkMode,
+ updateBodyDisplayStyle,
+} from "@/Cabinet/utils/displayStyleUtils";
+
+const DarkModeToggleSwitch = ({ id }: { id: string }) => {
+ const [toggleType, setToggleType] = useRecoilState(displayStyleState);
+ const darkModeQuery = isDeviceDarkMode();
+ const [displayStyleType, setDisplayStyleType] = useState(
+ () => {
+ return getInitialDisplayStyle(
+ getDisplayStyleFromLocalStorage(),
+ darkModeQuery
+ );
+ }
+ );
+
+ const isDarkMode = displayStyleType === DisplayStyleType.DARK;
+ const { updateToggleType, addDarkModeListener } = useDisplayStyleToggle();
+
+ useEffect(() => {
+ setToggleType(getDisplayStyleFromLocalStorage());
+ }, []);
+
+ useEffect(() => {
+ const updateDisplayStyleType = () => {
+ const newDisplayStyleType = getInitialDisplayStyle(
+ toggleType,
+ darkModeQuery
+ );
+ setDisplayStyleType(newDisplayStyleType);
+ };
+
+ updateDisplayStyleType();
+ addDarkModeListener(darkModeQuery, updateDisplayStyleType);
+ }, [toggleType]);
+
+ useEffect(() => {
+ updateBodyDisplayStyle(displayStyleType);
+ }, [displayStyleType]);
+
+ const handleToggleChange = useCallback(() => {
+ let newToggleType;
+ if (toggleType === DisplayStyleToggleType.DEVICE) {
+ newToggleType =
+ displayStyleType === DisplayStyleType.LIGHT
+ ? DisplayStyleToggleType.DARK
+ : DisplayStyleToggleType.LIGHT;
+ } else {
+ newToggleType =
+ toggleType === DisplayStyleToggleType.LIGHT
+ ? DisplayStyleToggleType.DARK
+ : DisplayStyleToggleType.LIGHT;
+ }
+
+ updateToggleType(newToggleType);
+ }, [displayStyleType]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const ToggleWrapperStyled = styled.div`
+ display: inline-block;
+ position: relative;
+ margin-right: 10px;
+`;
+
+const CheckboxStyled = styled.input.attrs({ type: "checkbox" })`
+ opacity: 0;
+ position: absolute;
+ width: 0;
+ height: 0;
+`;
+
+const ToggleSwitchStyled = styled.label<{ isChecked: boolean }>`
+ cursor: pointer;
+ display: inline-block;
+ position: relative;
+ background: ${(props) =>
+ props.isChecked ? "var(--sys-main-color)" : "var(--line-color)"};
+ width: 46px;
+ height: 24px;
+ border-radius: 50px;
+ transition: background-color 0.2s ease;
+`;
+
+const ToggleKnobStyled = styled.span<{ isChecked: boolean }>`
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ left: ${(props) => (props.isChecked ? "calc(100% - 21px)" : "3px")};
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ background: var(--white-text-with-bg-color);
+ transition: left 0.2s;
+`;
+
+const MoonIconWrapperStyled = styled.div`
+ position: absolute;
+ left: 5px;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 16px;
+ height: 16px;
+
+ & > svg {
+ width: 100%;
+ height: 100%;
+ fill: var(--ref-gray-900);
+ }
+`;
+
+const SunIconWrapperStyled = styled.div`
+ position: absolute;
+ right: 5px;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 18px;
+ height: 18px;
+
+ & > svg {
+ width: 100%;
+ height: 100%;
+ }
+`;
+
+export default DarkModeToggleSwitch;
diff --git a/frontend/src/Cabinet/components/LentLog/LogTable/AdminCabinetLogTable.tsx b/frontend/src/Cabinet/components/LentLog/LogTable/AdminCabinetLogTable.tsx
index f4c09b9c5..f330b294d 100644
--- a/frontend/src/Cabinet/components/LentLog/LogTable/AdminCabinetLogTable.tsx
+++ b/frontend/src/Cabinet/components/LentLog/LogTable/AdminCabinetLogTable.tsx
@@ -111,3 +111,4 @@ const EmptyLogStyled = styled.div`
`;
export default AdminCabinetLogTable;
+
\ No newline at end of file
diff --git a/frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx b/frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx
index 737ff86ae..600730b67 100644
--- a/frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx
+++ b/frontend/src/Cabinet/components/TopNav/TopNavDomainGroup/TopNavDomainGroup.tsx
@@ -1,5 +1,6 @@
import { useLocation, useNavigate } from "react-router-dom";
import styled from "styled-components";
+import DarkModeToggleSwitch from "@/Cabinet/components/Common/DarkModeToggleSwitch";
import { ReactComponent as CabiLogo } from "@/Cabinet/assets/images/logo.svg";
import { ReactComponent as PresentationLogo } from "@/Presentation/assets/images/logo.svg";
@@ -58,6 +59,9 @@ const TopNavDomainGroup = ({ isAdmin = false }: { isAdmin?: boolean }) => {
{index < domains.length - 1 && }
))}
+
+
+
);
};
@@ -127,4 +131,11 @@ const DomainSeparatorStyled = styled.div`
background-color: var(--service-man-title-border-btm-color);
`;
+const ToggleWrapperStyled = styled.div`
+ position: absolute;
+ right: 18px;
+ display: flex;
+ align-items: center;
+`;
+
export default TopNavDomainGroup;
diff --git a/frontend/src/Cabinet/hooks/useDisplayStyleToggle.ts b/frontend/src/Cabinet/hooks/useDisplayStyleToggle.ts
new file mode 100644
index 000000000..6d6df1356
--- /dev/null
+++ b/frontend/src/Cabinet/hooks/useDisplayStyleToggle.ts
@@ -0,0 +1,28 @@
+import { useRecoilState } from "recoil";
+import { displayStyleState } from "@/Cabinet/recoil/atoms";
+import { updateLocalStorageDisplayStyleToggle } from "@/Cabinet/components/Card/DisplayStyleCard/DisplayStyleCard";
+import { DisplayStyleToggleType } from "@/Cabinet/types/enum/displayStyle.type.enum";
+
+export const useDisplayStyleToggle = () => {
+ const [toggleType, setToggleType] = useRecoilState(displayStyleState);
+
+ const updateToggleType = (newToggleType: DisplayStyleToggleType) => {
+ setToggleType(newToggleType);
+ updateLocalStorageDisplayStyleToggle(newToggleType);
+ };
+
+ const addDarkModeListener = (
+ darkModeQuery: MediaQueryList,
+ callback: () => void
+ ) => {
+ if (toggleType === DisplayStyleToggleType.DEVICE) {
+ darkModeQuery.addEventListener("change", callback);
+ return () => {
+ darkModeQuery.removeEventListener("change", callback);
+ };
+ }
+ };
+ return { updateToggleType, addDarkModeListener };
+};
+
+export default useDisplayStyleToggle;
diff --git a/frontend/src/Cabinet/pages/admin/AdminSlackAlarmPage.tsx b/frontend/src/Cabinet/pages/admin/AdminSlackAlarmPage.tsx
index 94a2df9f0..030a49fcc 100644
--- a/frontend/src/Cabinet/pages/admin/AdminSlackAlarmPage.tsx
+++ b/frontend/src/Cabinet/pages/admin/AdminSlackAlarmPage.tsx
@@ -275,6 +275,7 @@ const FormSubTitleStyled = styled.h3`
`;
const FormTextareaStyled = styled.textarea`
+ color: var(--normal-text-color);
box-sizing: border-box;
width: 100%;
min-height: 200px;
diff --git a/frontend/src/Cabinet/recoil/atoms.ts b/frontend/src/Cabinet/recoil/atoms.ts
index cfc0711a8..e87eaa663 100644
--- a/frontend/src/Cabinet/recoil/atoms.ts
+++ b/frontend/src/Cabinet/recoil/atoms.ts
@@ -19,6 +19,7 @@ import {
import { ClubUserDto } from "@/Cabinet/types/dto/lent.dto";
import { UserDto, UserInfo } from "@/Cabinet/types/dto/user.dto";
import CabinetDetailAreaType from "@/Cabinet/types/enum/cabinetDetailArea.type.enum";
+import { DisplayStyleToggleType } from "@/Cabinet/types/enum/displayStyle.type.enum";
const { persistAtom } = recoilPersist();
@@ -213,3 +214,8 @@ export const currentFloorSectionNamesState = atom({
key: "currentFloorSectionNames",
default: [],
});
+
+export const displayStyleState = atom({
+ key: "displayStyle",
+ default: DisplayStyleToggleType.DEVICE,
+});
diff --git a/frontend/src/Cabinet/utils/displayStyleUtils.ts b/frontend/src/Cabinet/utils/displayStyleUtils.ts
new file mode 100644
index 000000000..04ea15343
--- /dev/null
+++ b/frontend/src/Cabinet/utils/displayStyleUtils.ts
@@ -0,0 +1,21 @@
+import {
+ DisplayStyleToggleType,
+ DisplayStyleType,
+} from "@/Cabinet/types/enum/displayStyle.type.enum";
+
+export const getDisplayStyleFromLocalStorage: () => DisplayStyleToggleType =
+ () => {
+ return (
+ (localStorage.getItem(
+ "display-style-toggle"
+ ) as DisplayStyleToggleType) || DisplayStyleToggleType.DEVICE
+ );
+ };
+
+export const updateBodyDisplayStyle = (style: DisplayStyleType) => {
+ document.body.setAttribute("display-style", style);
+};
+
+export const isDeviceDarkMode = () => {
+ return window.matchMedia("(prefers-color-scheme: dark)");
+};