@uniai-fe/ui-legacy-template 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +27 -0
  2. package/README.md +5 -0
  3. package/package.json +86 -0
  4. package/src/asset-url.ts +7 -0
  5. package/src/compare/compare.scss +1 -0
  6. package/src/compare/components/box/Container.tsx +33 -0
  7. package/src/compare/components/box/TextStrong.tsx +15 -0
  8. package/src/compare/components/box/TextSub.tsx +15 -0
  9. package/src/compare/components/box/index.tsx +11 -0
  10. package/src/compare/components/button/Remove.tsx +28 -0
  11. package/src/compare/components/button/index.tsx +5 -0
  12. package/src/compare/components/chart/Container.tsx +33 -0
  13. package/src/compare/components/chart/graph/Container.tsx +15 -0
  14. package/src/compare/components/chart/graph/Item.tsx +13 -0
  15. package/src/compare/components/chart/index.tsx +16 -0
  16. package/src/compare/components/chart/legend/Container.tsx +23 -0
  17. package/src/compare/components/chart/legend/Section.tsx +31 -0
  18. package/src/compare/components/select-list/Container.tsx +15 -0
  19. package/src/compare/components/select-list/Header.tsx +15 -0
  20. package/src/compare/components/select-list/Item.tsx +15 -0
  21. package/src/compare/components/select-list/List.tsx +18 -0
  22. package/src/compare/components/select-list/index.tsx +13 -0
  23. package/src/compare/index.tsx +13 -0
  24. package/src/compare/styled.tsx +17 -0
  25. package/src/compare/styles/scss/variables.scss +17 -0
  26. package/src/compare/styles/styled-components/chart/graph.ts +26 -0
  27. package/src/compare/styles/styled-components/chart/layout.ts +38 -0
  28. package/src/compare/styles/styled-components/chart/legend.ts +64 -0
  29. package/src/compare/styles/styled-components/common.ts +57 -0
  30. package/src/compare/styles/styled-components/select-list.ts +36 -0
  31. package/src/components.tsx +5 -0
  32. package/src/filter/components/Container.tsx +39 -0
  33. package/src/filter/components/Group.tsx +100 -0
  34. package/src/filter/components/ToggleButton.tsx +26 -0
  35. package/src/filter/index.tsx +5 -0
  36. package/src/filter/styled.tsx +3 -0
  37. package/src/filter/styles/styled-components/filter.ts +66 -0
  38. package/src/health-score/components/Chart.tsx +39 -0
  39. package/src/health-score/icon/HealthScore.tsx +46 -0
  40. package/src/health-score/icon/index.tsx +3 -0
  41. package/src/health-score/index.tsx +3 -0
  42. package/src/health-score/styled.tsx +3 -0
  43. package/src/health-score/styles/styled/chart.ts +27 -0
  44. package/src/index.scss +1 -0
  45. package/src/index.tsx +6 -0
  46. package/src/styled.tsx +5 -0
  47. package/src/types/compare/chart.d.ts +8 -0
  48. package/src/types/filter.d.ts +39 -0
  49. package/src/types/index.d.ts +2 -0
package/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 UNIAI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ This project includes third-party software governed by additional licenses,
26
+ including Apache License 2.0. Refer to `THIRD_PARTY_NOTICES.md` for the full
27
+ text of those notices and any required attributions.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # client / ui-legacy-template
2
+
3
+ `deprecated`
4
+ 스타일링 도구와 토큰, 디자인 시스템, 컴포넌트 등 관리
5
+ -> `design/*`로 이동
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@uniai-fe/ui-legacy-template",
3
+ "version": "0.1.0",
4
+ "description": "Legacy UI Templates Toolkit for UNIAI FE Projects",
5
+ "type": "module",
6
+ "private": false,
7
+ "sideEffects": false,
8
+ "license": "MIT",
9
+ "homepage": "https://www.uniai.co.kr/",
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "packageManager": "pnpm@10.23.0",
14
+ "engines": {
15
+ "node": ">=24",
16
+ "pnpm": ">=10"
17
+ },
18
+ "author": {
19
+ "name": "GraffitoRyu",
20
+ "email": "yth4135@naver.com",
21
+ "url": "https://github.com/GraffitoRyu"
22
+ },
23
+ "files": [
24
+ "src"
25
+ ],
26
+ "scripts": {
27
+ "lint": "eslint . --max-warnings=0",
28
+ "typecheck": "tsc --project tsconfig.build.json --noEmit",
29
+ "build": "pnpm typecheck",
30
+ "dev": "tsc --project tsconfig.build.json --watch --noEmit",
31
+ "module:lint": "pnpm lint",
32
+ "module:typecheck": "pnpm typecheck",
33
+ "module:build": "pnpm build",
34
+ "ui-legacy-template:build": "pnpm run build",
35
+ "ui-legacy-template:dev": "pnpm run dev"
36
+ },
37
+ "module": "./src/index.tsx",
38
+ "main": "./src/index.tsx",
39
+ "types": "./src/types/index.d.ts",
40
+ "exports": {
41
+ ".": "./src/index.tsx",
42
+ "./types": "./src/types/index.d.ts",
43
+ "./components": "./src/components.tsx"
44
+ },
45
+ "peerDependencies": {
46
+ "jotai": ">= 2",
47
+ "next": ">= 15",
48
+ "react": ">= 19",
49
+ "react-dom": ">= 19",
50
+ "sass": ">= 15",
51
+ "styled-components": ">= 6",
52
+ "react-hook-form": ">= 7",
53
+ "@uniai-fe/chart-legacy": ">= 0.1.0",
54
+ "@uniai-fe/ui-legacy": ">= 0.1.7"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "next": {
58
+ "optional": true
59
+ }
60
+ },
61
+ "devDependencies": {
62
+ "@types/node": "^24.10.1",
63
+ "@types/react": "^19.2.7",
64
+ "@types/react-dom": "^19.2.3",
65
+ "@uniai-fe/chart-legacy": "workspace:*",
66
+ "@uniai-fe/eslint-config": "workspace:*",
67
+ "@uniai-fe/next-devkit": "workspace:*",
68
+ "@uniai-fe/react-hooks": "workspace:*",
69
+ "@uniai-fe/tsconfig": "workspace:*",
70
+ "@uniai-fe/ui-legacy": "workspace:*",
71
+ "@uniai-fe/util-functions": "workspace:*",
72
+ "@uniai-fe/util-react": "workspace:*",
73
+ "autoprefixer": "^10.4.22",
74
+ "clsx": "^2.1.1",
75
+ "eslint": "^9.39.1",
76
+ "jotai": "^2.15.1",
77
+ "prettier": "^3.6.2",
78
+ "next": "^15.5.6",
79
+ "react": "^19.2.0",
80
+ "react-dom": "^19.2.0",
81
+ "react-hook-form": "^7.66.1",
82
+ "sass": "^1.94.2",
83
+ "styled-components": "^6.1.19",
84
+ "typescript": "~5.9.3"
85
+ }
86
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 이미지 호스팅 경로
3
+ */
4
+ const assetUrl: string =
5
+ "https://raw.githubusercontent.com/uniai-corp/makeup/refs/heads/main/assets";
6
+
7
+ export default assetUrl;
@@ -0,0 +1 @@
1
+ @use "./styles/scss/variables.scss";
@@ -0,0 +1,33 @@
1
+ "use client";
2
+
3
+ import CompareRemoveButton from "../button/Remove";
4
+ import StyledCompareCommon from "../../styles/styled-components/common";
5
+ import type { ComponentBaseProps } from "@uniai-fe/ui-legacy";
6
+
7
+ /**
8
+ * 비교 템플릿; 박스
9
+ * @component
10
+ * @param {object} props
11
+ * @param {boolean} [props.isDashed] 테두리 점선 여부
12
+ * @param {boolean} [props.isSelected] 선택 여부
13
+ * @param {Function} props.onRemove 선택해제 이벤트
14
+ */
15
+ export default function CompareBoxContainer({
16
+ className,
17
+ isDashed,
18
+ isSelected,
19
+ onRemove,
20
+ children,
21
+ }: ComponentBaseProps &
22
+ Partial<{
23
+ isDashed: boolean;
24
+ isSelected: boolean;
25
+ onRemove: () => void;
26
+ }>) {
27
+ return (
28
+ <StyledCompareCommon.box $isDashed={isDashed} className={className}>
29
+ {children}
30
+ {isSelected && onRemove && <CompareRemoveButton onRemove={onRemove} />}
31
+ </StyledCompareCommon.box>
32
+ );
33
+ }
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ import type { ComponentBaseProps } from "@uniai-fe/ui-legacy";
4
+ import StyledCompareCommon from "../../styles/styled-components/common";
5
+
6
+ export default function CompareTextStrong({
7
+ className,
8
+ children,
9
+ }: ComponentBaseProps) {
10
+ return (
11
+ <StyledCompareCommon.text.strong className={className}>
12
+ {children}
13
+ </StyledCompareCommon.text.strong>
14
+ );
15
+ }
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ import type { ComponentBaseProps } from "@uniai-fe/ui-legacy";
4
+ import StyledCompareCommon from "../../styles/styled-components/common";
5
+
6
+ export default function CompareTextSub({
7
+ className,
8
+ children,
9
+ }: ComponentBaseProps) {
10
+ return (
11
+ <StyledCompareCommon.text.sub className={className}>
12
+ {children}
13
+ </StyledCompareCommon.text.sub>
14
+ );
15
+ }
@@ -0,0 +1,11 @@
1
+ import CompareBoxContainer from "./Container";
2
+ import CompareTextStrong from "./TextStrong";
3
+ import CompareTextSub from "./TextSub";
4
+
5
+ const CompareBox = {
6
+ Container: CompareBoxContainer,
7
+ TextStrong: CompareTextStrong,
8
+ TextSub: CompareTextSub,
9
+ };
10
+
11
+ export default CompareBox;
@@ -0,0 +1,28 @@
1
+ "use client";
2
+
3
+ import { Icon } from "@uniai-fe/ui-legacy";
4
+ import StyledCompareCommon from "../../styles/styled-components/common";
5
+
6
+ /**
7
+ * 비교 템플릿; 선택해제 버튼
8
+ * @component
9
+ * @param {object} props
10
+ * @param {Function} props.onRemove 선택해제 이벤트
11
+ */
12
+ export default function CompareRemoveButton({
13
+ className,
14
+ onRemove,
15
+ }: {
16
+ className?: string;
17
+ onRemove: () => void;
18
+ }) {
19
+ return (
20
+ <StyledCompareCommon.button.remove
21
+ type="button"
22
+ className={className}
23
+ onClick={onRemove}
24
+ >
25
+ <Icon.Button.Remove.Solid />
26
+ </StyledCompareCommon.button.remove>
27
+ );
28
+ }
@@ -0,0 +1,5 @@
1
+ import CompareRemoveButton from "./Remove";
2
+
3
+ const CompareButton = { Remove: CompareRemoveButton };
4
+
5
+ export default CompareButton;
@@ -0,0 +1,33 @@
1
+ "use client";
2
+
3
+ import type { CompareChartLegendCategoryDataType } from "../../../types";
4
+ import StyledCompare from "../../styled";
5
+ import CompareChartLegendContainer from "./legend/Container";
6
+ import CompareChartGraphContainer from "./graph/Container";
7
+ import { Scrollbar } from "@uniai-fe/ui-legacy";
8
+
9
+ export default function CompareChartContainer({
10
+ leftWidth,
11
+ legendCategoryData,
12
+ children: graphItems,
13
+ }: {
14
+ legendCategoryData: CompareChartLegendCategoryDataType[];
15
+ children: React.ReactNode;
16
+ } & Partial<{ leftWidth: number | string }>) {
17
+ return (
18
+ <StyledCompare.chart.container>
19
+ <Scrollbar.Container>
20
+ <StyledCompare.chart.wrapper>
21
+ <StyledCompare.chart.left $leftWidth={leftWidth}>
22
+ <CompareChartLegendContainer categoryData={legendCategoryData} />
23
+ </StyledCompare.chart.left>
24
+ <StyledCompare.chart.right $leftWidth={leftWidth}>
25
+ <CompareChartGraphContainer>
26
+ {graphItems}
27
+ </CompareChartGraphContainer>
28
+ </StyledCompare.chart.right>
29
+ </StyledCompare.chart.wrapper>
30
+ </Scrollbar.Container>
31
+ </StyledCompare.chart.container>
32
+ );
33
+ }
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ import StyledCompareChartGraph from "../../../styles/styled-components/chart/graph";
4
+
5
+ export default function CompareChartGraphContainer({
6
+ children,
7
+ }: {
8
+ children: React.ReactNode;
9
+ }) {
10
+ return (
11
+ <StyledCompareChartGraph.container>
12
+ {children}
13
+ </StyledCompareChartGraph.container>
14
+ );
15
+ }
@@ -0,0 +1,13 @@
1
+ "use client";
2
+
3
+ import StyledCompareChartGraph from "../../../styles/styled-components/chart/graph";
4
+
5
+ export default function CompareChartGraphItem({
6
+ children,
7
+ }: {
8
+ children: React.ReactNode;
9
+ }) {
10
+ return (
11
+ <StyledCompareChartGraph.item>{children}</StyledCompareChartGraph.item>
12
+ );
13
+ }
@@ -0,0 +1,16 @@
1
+ import CompareChartContainer from "./Container";
2
+ import CompareChartGraphContainer from "./graph/Container";
3
+ import CompareChartGraphItem from "./graph/Item";
4
+ import CompareChartLegendContainer from "./legend/Container";
5
+ import CompareChartLegendSectionContainer from "./legend/Section";
6
+
7
+ const CompareChart = {
8
+ Container: CompareChartContainer,
9
+ Legend: {
10
+ Container: CompareChartLegendContainer,
11
+ Section: CompareChartLegendSectionContainer,
12
+ },
13
+ Graph: { Container: CompareChartGraphContainer, Item: CompareChartGraphItem },
14
+ };
15
+
16
+ export default CompareChart;
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import type { CompareChartLegendCategoryDataType } from "../../../../types";
4
+ import StyledCompare from "../../../styled";
5
+ import CompareChartLegendSectionContainer from "./Section";
6
+
7
+ export default function CompareChartLegendContainer({
8
+ categoryData,
9
+ }: {
10
+ categoryData: CompareChartLegendCategoryDataType[];
11
+ }) {
12
+ return (
13
+ <StyledCompare.chart.legend.container>
14
+ {categoryData.map(({ key, title, legend }) => (
15
+ <CompareChartLegendSectionContainer
16
+ key={key}
17
+ dataName={title}
18
+ legendData={legend}
19
+ />
20
+ ))}
21
+ </StyledCompare.chart.legend.container>
22
+ );
23
+ }
@@ -0,0 +1,31 @@
1
+ "use client";
2
+
3
+ import type { ChartLegendBaseType } from "@uniai-fe/chart-legacy";
4
+ import StyledCompare from "../../../styled";
5
+
6
+ export default function CompareChartLegendSectionContainer({
7
+ dataName,
8
+ legendData,
9
+ }: {
10
+ dataName: React.ReactNode;
11
+ legendData: ChartLegendBaseType[];
12
+ }) {
13
+ return (
14
+ <StyledCompare.chart.legend.section.container>
15
+ <StyledCompare.chart.legend.section.header>
16
+ <h4>{dataName}</h4>
17
+ </StyledCompare.chart.legend.section.header>
18
+ <StyledCompare.chart.legend.section.contents>
19
+ {legendData.map(({ key, name, color }) => (
20
+ <StyledCompare.chart.legend.section.legendItem
21
+ key={key}
22
+ $color={color}
23
+ >
24
+ <figure></figure>
25
+ <span>{name}</span>
26
+ </StyledCompare.chart.legend.section.legendItem>
27
+ ))}
28
+ </StyledCompare.chart.legend.section.contents>
29
+ </StyledCompare.chart.legend.section.container>
30
+ );
31
+ }
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ import type { ComponentBaseProps } from "@uniai-fe/ui-legacy";
4
+ import StyledCompareSelect from "../../styles/styled-components/select-list";
5
+
6
+ export default function CompareSelectContainer({
7
+ className,
8
+ children,
9
+ }: ComponentBaseProps) {
10
+ return (
11
+ <StyledCompareSelect.container className={className}>
12
+ {children}
13
+ </StyledCompareSelect.container>
14
+ );
15
+ }
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ import type { ComponentBaseProps } from "@uniai-fe/ui-legacy";
4
+ import StyledCompareSelect from "../../styles/styled-components/select-list";
5
+
6
+ export default function CompareSelectHeader({
7
+ className,
8
+ children,
9
+ }: ComponentBaseProps) {
10
+ return (
11
+ <StyledCompareSelect.header className={className}>
12
+ {children}
13
+ </StyledCompareSelect.header>
14
+ );
15
+ }
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ import type { ComponentBaseProps } from "@uniai-fe/ui-legacy";
4
+ import StyledCompareSelect from "../../styles/styled-components/select-list";
5
+
6
+ export default function CompareSelectListItem({
7
+ className,
8
+ children,
9
+ }: ComponentBaseProps) {
10
+ return (
11
+ <StyledCompareSelect.item className={className}>
12
+ {children}
13
+ </StyledCompareSelect.item>
14
+ );
15
+ }
@@ -0,0 +1,18 @@
1
+ "use client";
2
+
3
+ import type { ComponentBaseProps } from "@uniai-fe/ui-legacy";
4
+ import StyledCompareSelect from "../../styles/styled-components/select-list";
5
+
6
+ export default function CompareSelectList({
7
+ className,
8
+ columnLength,
9
+ children,
10
+ }: {
11
+ columnLength: number;
12
+ } & ComponentBaseProps) {
13
+ return (
14
+ <StyledCompareSelect.list className={className} $length={columnLength}>
15
+ {children}
16
+ </StyledCompareSelect.list>
17
+ );
18
+ }
@@ -0,0 +1,13 @@
1
+ import CompareSelectContainer from "./Container";
2
+ import CompareSelectHeader from "./Header";
3
+ import CompareSelectList from "./List";
4
+ import CompareSelectListItem from "./Item";
5
+
6
+ const CompareSelect = {
7
+ Container: CompareSelectContainer,
8
+ Header: CompareSelectHeader,
9
+ List: CompareSelectList,
10
+ Item: CompareSelectListItem,
11
+ };
12
+
13
+ export default CompareSelect;
@@ -0,0 +1,13 @@
1
+ import CompareBox from "./components/box";
2
+ import CompareButton from "./components/button";
3
+ import CompareChart from "./components/chart";
4
+ import CompareSelect from "./components/select-list";
5
+
6
+ const Compare = {
7
+ Select: CompareSelect,
8
+ Button: CompareButton,
9
+ Box: CompareBox,
10
+ Chart: CompareChart,
11
+ };
12
+
13
+ export default Compare;
@@ -0,0 +1,17 @@
1
+ import StyledCompareChartGraph from "./styles/styled-components/chart/graph";
2
+ import StyledCompareChartLayout from "./styles/styled-components/chart/layout";
3
+ import StyledCompareChartLegend from "./styles/styled-components/chart/legend";
4
+ import StyledCompareCommon from "./styles/styled-components/common";
5
+ import StyledCompareSelect from "./styles/styled-components/select-list";
6
+
7
+ const StyledCompare = {
8
+ ...StyledCompareCommon,
9
+ select: StyledCompareSelect,
10
+ chart: {
11
+ ...StyledCompareChartLayout,
12
+ legend: StyledCompareChartLegend,
13
+ graph: StyledCompareChartGraph,
14
+ },
15
+ };
16
+
17
+ export default StyledCompare;
@@ -0,0 +1,17 @@
1
+ :root {
2
+ --compare-select-list-item-margin: 3rem;
3
+
4
+ --compare-box-padding: 1.5rem;
5
+ --compare-box-text-margin: 1rem;
6
+ --compare-box-text-strong-size: 1.8rem;
7
+ --compare-box-text-sub-size: 1.6rem;
8
+
9
+ --compare-box-height: 2px + var(--compare-box-padding) * 2 +
10
+ var(--compare-box-text-strong-size) + var(--compare-box-text-sub-size) +
11
+ var(--compare-box-text-margin);
12
+
13
+ --compare-chart-item-padding: 2rem;
14
+ --compare-chart-legend-header-height: 3.5rem;
15
+ --compare-chart-legend-header-margin: 1.5rem;
16
+ --compare-chart-legend-contents-height: 8rem;
17
+ }
@@ -0,0 +1,26 @@
1
+ "use client";
2
+
3
+ import { styled } from "styled-components";
4
+
5
+ const container = styled.div`
6
+ width: 100%;
7
+ height: 100%;
8
+ padding: 0 var(--page-padding-default);
9
+ `;
10
+ const item = styled.div`
11
+ width: 100%;
12
+ height: calc(
13
+ var(--compare-chart-item-padding) +
14
+ var(--compare-chart-legend-header-height) +
15
+ var(--compare-chart-legend-header-margin) +
16
+ var(--compare-chart-legend-contents-height)
17
+ );
18
+ margin-top: var(--compare-chart-item-padding);
19
+ background-color: var(--theme-background-20);
20
+ `;
21
+ const StyledCompareChartGraph = {
22
+ container,
23
+ item,
24
+ };
25
+
26
+ export default StyledCompareChartGraph;
@@ -0,0 +1,38 @@
1
+ "use client";
2
+
3
+ import { styled } from "styled-components";
4
+ import { styleBaseSize } from "@uniai-fe/util-functions";
5
+
6
+ type StyledLayoutProps = Partial<{
7
+ $leftWidth: number | string;
8
+ }>;
9
+ const size = ({ $leftWidth }: StyledLayoutProps) =>
10
+ styleBaseSize("px", $leftWidth, 300);
11
+ const container = styled.div`
12
+ width: 100%;
13
+ height: 100%;
14
+ border: 1px solid var(--color_30);
15
+ background-color: var(--color_0);
16
+ display: flex;
17
+ `;
18
+ const wrapper = styled.div`
19
+ width: 100%;
20
+ height: 100%;
21
+ display: flex;
22
+ `;
23
+ const left = styled.div<StyledLayoutProps>`
24
+ width: ${size};
25
+ `;
26
+ const right = styled.div<StyledLayoutProps>`
27
+ width: calc(100% - ${size});
28
+ border-left: 1px solid var(--color_30);
29
+ `;
30
+
31
+ const StyledCompareChartLayout = {
32
+ container,
33
+ wrapper,
34
+ left,
35
+ right,
36
+ };
37
+
38
+ export default StyledCompareChartLayout;
@@ -0,0 +1,64 @@
1
+ "use client";
2
+
3
+ import { styled } from "styled-components";
4
+
5
+ const container = styled.ul`
6
+ width: 100%;
7
+ height: 100%;
8
+ `;
9
+
10
+ const sectionContainer = styled.li`
11
+ width: 100%;
12
+ padding: var(--compare-chart-item-padding);
13
+ `;
14
+ const sectionHeader = styled.header`
15
+ height: var(--compare-chart-legend-header-height);
16
+ border-bottom: 1px solid var(--color_30);
17
+ margin-bottom: var(--compare-chart-legend-header-margin);
18
+
19
+ h4 {
20
+ font-size: 18px;
21
+ color: var(--color_90);
22
+ font-weight: 600;
23
+ line-height: 1em;
24
+ }
25
+ `;
26
+ type StyledLegendItemProps = Partial<{
27
+ $color: string;
28
+ }>;
29
+ const sectionContents = styled.ul<StyledLegendItemProps>`
30
+ width: 100%;
31
+ height: var(--compare-chart-legend-contents-height);
32
+ `;
33
+ const sectionLegendItem = styled.li<StyledLegendItemProps>`
34
+ margin-top: 10px;
35
+ display: flex;
36
+ align-items: center;
37
+
38
+ figure {
39
+ width: 12px;
40
+ height: 12px;
41
+ margin-right: 8px;
42
+ border-radius: 4px;
43
+ background-color: ${({ $color }) => ($color ? $color : "var(--color_40)")};
44
+ }
45
+
46
+ span {
47
+ font-size: 16px;
48
+ color: var(--color_80);
49
+ }
50
+ `;
51
+
52
+ const section = {
53
+ container: sectionContainer,
54
+ header: sectionHeader,
55
+ contents: sectionContents,
56
+ legendItem: sectionLegendItem,
57
+ };
58
+
59
+ const StyledCompareChartLegend = {
60
+ container,
61
+ section,
62
+ };
63
+
64
+ export default StyledCompareChartLegend;
@@ -0,0 +1,57 @@
1
+ "use client";
2
+
3
+ import { styled } from "styled-components";
4
+
5
+ const box = styled.div<{ $isDashed?: boolean }>`
6
+ width: 100%;
7
+ height: 100%;
8
+ padding: var(--compare-box-padding);
9
+ border-radius: 0.8rem;
10
+ border: 1px ${({ $isDashed }) => ($isDashed ? "dashed" : "solid")}
11
+ var(--color_30);
12
+ position: relative;
13
+ `;
14
+
15
+ const boxText = styled.span`
16
+ display: block;
17
+ `;
18
+
19
+ const textStrong = styled(boxText)`
20
+ font-size: var(--compare-box-text-strong-size);
21
+ color: var(--color_90);
22
+ `;
23
+
24
+ const textSub = styled(boxText)`
25
+ font-size: var(--compare-box-text-sub-size);
26
+ color: var(--color_60);
27
+ `;
28
+
29
+ const text = {
30
+ common: boxText,
31
+ strong: textStrong,
32
+ sub: textSub,
33
+ };
34
+
35
+ const remove = styled.button`
36
+ width: fit-content;
37
+ height: fit-content;
38
+ font-size: 0;
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ position: absolute;
43
+ top: 1rem;
44
+ right: 1rem;
45
+ `;
46
+
47
+ const button = {
48
+ remove,
49
+ };
50
+
51
+ const StyledCompareCommon = {
52
+ box,
53
+ text,
54
+ button,
55
+ };
56
+
57
+ export default StyledCompareCommon;
@@ -0,0 +1,36 @@
1
+ "use client";
2
+
3
+ import { styled } from "styled-components";
4
+
5
+ const container = styled.div`
6
+ width: calc(100% + var(--compare-select-list-item-margin));
7
+ margin: 0 calc(var(--compare-select-list-item-margin) / -2);
8
+ `;
9
+
10
+ const header = styled.header`
11
+ width: 100%;
12
+ margin-bottom: 2rem;
13
+ display: flex;
14
+ align-items: center;
15
+ `;
16
+
17
+ const item = styled.li`
18
+ padding: 0 calc(var(--compare-select-list-item-margin) / 2);
19
+ `;
20
+
21
+ const list = styled.ul<{ $length: number }>`
22
+ width: 100%;
23
+ display: flex;
24
+ ${item} {
25
+ width: calc(100% / ${({ $length }) => $length});
26
+ }
27
+ `;
28
+
29
+ const StyledCompareSelect = {
30
+ container,
31
+ header,
32
+ list,
33
+ item,
34
+ };
35
+
36
+ export default StyledCompareSelect;
@@ -0,0 +1,5 @@
1
+ import HealthScoreChart from "./health-score";
2
+ import Compare from "./compare";
3
+ import Filter from "./filter";
4
+
5
+ export { HealthScoreChart, Compare, Filter };
@@ -0,0 +1,39 @@
1
+ "use client";
2
+
3
+ import { useCallback, useRef, useState } from "react";
4
+
5
+ import { Dropdown } from "@uniai-fe/ui-legacy";
6
+ import FilterGroup from "./Group";
7
+ import FilterToggleButton from "./ToggleButton";
8
+
9
+ import StyledFilter from "../styles/styled-components/filter";
10
+ import type { FilterGroupDataType } from "../../types";
11
+ import { useClickOutside } from "@uniai-fe/react-hooks";
12
+
13
+ export default function FilterContainer({
14
+ group,
15
+ }: {
16
+ group: FilterGroupDataType[];
17
+ }) {
18
+ const [isOpen, setOpen] = useState<boolean>(false);
19
+ const onToggle = useCallback(() => {
20
+ setOpen(prev => !prev);
21
+ }, []);
22
+ const onClose = useCallback(() => {
23
+ setOpen(false);
24
+ }, []);
25
+
26
+ const dropdownRef = useRef<HTMLDivElement | null>(null);
27
+ useClickOutside(dropdownRef, onClose);
28
+
29
+ return (
30
+ <StyledFilter.Parent ref={dropdownRef}>
31
+ <FilterToggleButton clickEvent={onToggle} />
32
+ <Dropdown.Container isOpen={isOpen} horizontal="right" gap={7}>
33
+ {group.map(d => (
34
+ <FilterGroup {...d} key={d.groupKey} />
35
+ ))}
36
+ </Dropdown.Container>
37
+ </StyledFilter.Parent>
38
+ );
39
+ }
@@ -0,0 +1,100 @@
1
+ "use client";
2
+
3
+ import { useCallback } from "react";
4
+ import { useFormContext, useWatch } from "react-hook-form";
5
+
6
+ import { Input } from "@uniai-fe/ui-legacy";
7
+ import StyledFilter from "../styles/styled-components/filter";
8
+
9
+ import type { FilterGroupDataType, FilterStateFormType } from "../../types";
10
+
11
+ export default function FilterGroup({
12
+ groupKey,
13
+ categoryKey,
14
+ categoryName,
15
+ items,
16
+ }: FilterGroupDataType) {
17
+ const { register, control, getValues, setValue } =
18
+ useFormContext<FilterStateFormType>();
19
+
20
+ // 현재 그룹 선택 현황
21
+ const checkState = useWatch({ control, name: `filter.${categoryKey}` });
22
+
23
+ // 전체선택 클릭 이벤트
24
+ const onCheckAll = useCallback(
25
+ (e: React.ChangeEvent<HTMLInputElement>) => {
26
+ const { checked } = e.target;
27
+ setValue(`filter.${categoryKey}.all`, checked);
28
+
29
+ // 하위항목에 all check상태 일괄 적용
30
+ const keys = items.map(({ value }) => value);
31
+
32
+ keys.forEach(dataKey => {
33
+ setValue(`filter.${categoryKey}.${dataKey}`, checked);
34
+ });
35
+ },
36
+ [categoryKey, items, setValue],
37
+ );
38
+
39
+ // 하위항목 선택 클릭 이벤트
40
+ const onCheckEach = useCallback(
41
+ (e: React.ChangeEvent<HTMLInputElement>, dataKey: string) => {
42
+ // 클릭한 항목 선택 업데이트
43
+ const { checked: thisChecked } = e.target;
44
+ setValue(`filter.${categoryKey}.${dataKey}`, thisChecked);
45
+
46
+ // 전체 선택상태
47
+ const currentCheckedAll = getValues(`filter.${categoryKey}.all`);
48
+
49
+ // 나머지 하위항목 선택상태
50
+ const others = Object.fromEntries(
51
+ Object.entries(checkState).filter(
52
+ ([key]) => !["all", dataKey].includes(key),
53
+ ),
54
+ );
55
+ // 현재 항목을 클릭함으로써, 모든 하위항목이 모두 체크되었는지 확인
56
+ const isCheckedAllByEach =
57
+ thisChecked && Object.values(others).every(checked => checked);
58
+
59
+ // 현재 전체선택 체크상태와 다르면 업데이트
60
+ if (currentCheckedAll !== isCheckedAllByEach)
61
+ setValue(`filter.${categoryKey}.all`, isCheckedAllByEach);
62
+ },
63
+ [categoryKey, checkState, getValues, setValue],
64
+ );
65
+
66
+ return (
67
+ <StyledFilter.Group.Container>
68
+ <StyledFilter.Group.Title>
69
+ <Input.Checkbox
70
+ label={{
71
+ name: <span>{categoryName}</span>,
72
+ position: "right",
73
+ hide: false,
74
+ }}
75
+ selectorId={`${categoryKey}/all`}
76
+ dataKey={`${categoryKey}/all`}
77
+ register={register(`filter.${categoryKey}.all`)}
78
+ state={{ checked: checkState.all }}
79
+ onCustomChange={onCheckAll}
80
+ />
81
+ </StyledFilter.Group.Title>
82
+ {items.map(({ value: dataKey, name }) => (
83
+ <StyledFilter.Group.Item key={`${groupKey}/${dataKey}`}>
84
+ <Input.Checkbox
85
+ label={{
86
+ name: <span>{name}</span>,
87
+ position: "right",
88
+ hide: false,
89
+ }}
90
+ selectorId={`${categoryKey}/${dataKey}`}
91
+ dataKey={`${categoryKey}/${dataKey}`}
92
+ register={register(`filter.${categoryKey}.${dataKey}`)}
93
+ state={{ checked: checkState[dataKey] }}
94
+ onCustomChange={e => onCheckEach(e, dataKey)}
95
+ />
96
+ </StyledFilter.Group.Item>
97
+ ))}
98
+ </StyledFilter.Group.Container>
99
+ );
100
+ }
@@ -0,0 +1,26 @@
1
+ "use client";
2
+
3
+ import { Button, Icon } from "@uniai-fe/ui-legacy";
4
+ import StyledFilter from "../styles/styled-components/filter";
5
+
6
+ export default function FilterToggleButton({
7
+ clickEvent,
8
+ }: {
9
+ clickEvent: () => void;
10
+ }) {
11
+ return (
12
+ <StyledFilter.Button
13
+ as={Button.Default}
14
+ className="filter-button"
15
+ styleOptions={{
16
+ priorityType: "pale",
17
+ fillType: "outline",
18
+ sizeType: "small",
19
+ }}
20
+ clickEvent={clickEvent}
21
+ >
22
+ <Icon.Button.Filter />
23
+ <span>필터</span>
24
+ </StyledFilter.Button>
25
+ );
26
+ }
@@ -0,0 +1,5 @@
1
+ import FilterContainer from "./components/Container";
2
+
3
+ const Filter = FilterContainer;
4
+
5
+ export default Filter;
@@ -0,0 +1,3 @@
1
+ import StyledFilter from "./styles/styled-components/filter";
2
+
3
+ export default StyledFilter;
@@ -0,0 +1,66 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { Button, StyledDropdown } from "@uniai-fe/ui-legacy";
5
+ import { styled } from "styled-components";
6
+
7
+ const FilterButton = styled(Button.Default as React.FC)`
8
+ margin: 0;
9
+ figure {
10
+ margin-right: 4px;
11
+ }
12
+ `;
13
+ const Parent = styled(StyledDropdown.parent)`
14
+ --dropdown-padding-vertical: 15px;
15
+ --dropdown-padding-horizontal: 15px;
16
+
17
+ --dropdown-border-top-radius: 4px;
18
+ --dropdown-border-bottom-radius: 4px;
19
+
20
+ --field-item-width: fit-content;
21
+ --field-item-wrapper-width: fit-content;
22
+ --field-item-flex-wrap: nowrap;
23
+
24
+ ${StyledDropdown.wrapper} {
25
+ display: flex;
26
+ }
27
+ `;
28
+
29
+ const GroupContainer = styled.dl`
30
+ width: fit-content;
31
+ margin-right: 15px;
32
+ padding-right: 15px;
33
+ border-right: 1px solid var(--color_30);
34
+
35
+ &:last-child {
36
+ margin-right: 0;
37
+ padding-right: 0;
38
+ border-right: 0;
39
+ }
40
+ `;
41
+
42
+ const GroupTitle = styled.dt`
43
+ padding-bottom: 10px;
44
+ border-bottom: 1px solid var(--color_30);
45
+ span {
46
+ font-size: 16px;
47
+ }
48
+ `;
49
+
50
+ const GroupItem = styled.dd`
51
+ margin-top: 5px;
52
+ `;
53
+
54
+ const Group = {
55
+ Container: GroupContainer,
56
+ Title: GroupTitle,
57
+ Item: GroupItem,
58
+ };
59
+
60
+ const StyledFilter = {
61
+ Parent,
62
+ Button: FilterButton,
63
+ Group,
64
+ };
65
+
66
+ export default StyledFilter;
@@ -0,0 +1,39 @@
1
+ "use client";
2
+
3
+ import { useCallback } from "react";
4
+ import { Chart } from "@uniai-fe/chart-legacy";
5
+ import StyledHealthScore from "../styles/styled/chart";
6
+ import { Button } from "@uniai-fe/ui-legacy";
7
+ // import Button from "@uniai-fe/ui-legacy";
8
+
9
+ export default function HealthScoreChart({
10
+ title,
11
+ score,
12
+ details,
13
+ }: {
14
+ title: string;
15
+ score: number | "";
16
+ details?: React.ReactNode[];
17
+ }) {
18
+ const onOpen = useCallback(() => {
19
+ console.log(details);
20
+ }, [details]);
21
+
22
+ return (
23
+ <StyledHealthScore.container>
24
+ <Chart.Graph.ArcMeter
25
+ data={{ name: title, value: score || "" }}
26
+ max={100}
27
+ />
28
+ <StyledHealthScore.title>
29
+ <h5>{title}</h5>
30
+ <Button.Tooltip
31
+ size={18}
32
+ priorityType="primary"
33
+ fillType="solid"
34
+ clickEvent={onOpen}
35
+ />
36
+ </StyledHealthScore.title>
37
+ </StyledHealthScore.container>
38
+ );
39
+ }
@@ -0,0 +1,46 @@
1
+ "use client";
2
+
3
+ import Image from "next/image";
4
+ import { useMemo } from "react";
5
+ import assetsUrl from "../../asset-url";
6
+
7
+ /**
8
+ * 건강도 아이콘 컴포넌트
9
+ * @component
10
+ * @param {object} props
11
+ * @param {boolean} props.isFlockExist 활성화 계군 존재여부
12
+ * @param {number|""} props.healthScore 건강도 값
13
+ */
14
+ export default function IconHealthScore({
15
+ size = 45,
16
+ isFlockExist,
17
+ healthScore,
18
+ }: {
19
+ size?: number;
20
+ isFlockExist: boolean;
21
+ healthScore: number | "";
22
+ }) {
23
+ const iconName = useMemo(() => {
24
+ if (typeof healthScore === "undefined" || !isFlockExist) return "empty";
25
+
26
+ if (isNaN(Number(healthScore)) || healthScore === "") return "gray";
27
+
28
+ if (healthScore < 25) return "red";
29
+ else if (25 <= healthScore && healthScore < 50) return "yellow";
30
+ else if (50 <= healthScore && healthScore < 75) return "green";
31
+ else if (75 <= healthScore) return "blue";
32
+
33
+ return "gray";
34
+ }, [healthScore, isFlockExist]);
35
+
36
+ return (
37
+ <figure>
38
+ <Image
39
+ src={`${assetsUrl}/img/health-score/${iconName}.svg`}
40
+ alt="건강"
41
+ width={size}
42
+ height={size}
43
+ />
44
+ </figure>
45
+ );
46
+ }
@@ -0,0 +1,3 @@
1
+ import IconHealthScore from "./HealthScore";
2
+
3
+ export default IconHealthScore;
@@ -0,0 +1,3 @@
1
+ import HealthScoreChart from "./components/Chart";
2
+
3
+ export default HealthScoreChart;
@@ -0,0 +1,3 @@
1
+ import StyledHealthScore from "./styles/styled/chart";
2
+
3
+ export default StyledHealthScore;
@@ -0,0 +1,27 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { Chart } from "@uniai-fe/chart-legacy";
5
+ import { styled } from "styled-components";
6
+
7
+ const container = styled(Chart.ContentsContainer as React.FC)``;
8
+
9
+ const title = styled.div`
10
+ width: 100%;
11
+ margin-top: 10px;
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ h5 {
16
+ font-size: 20px;
17
+ color: var(--color_90);
18
+ font-weight: 600;
19
+ }
20
+ `;
21
+
22
+ const StyledHealthScore = {
23
+ container,
24
+ title,
25
+ };
26
+
27
+ export default StyledHealthScore;
package/src/index.scss ADDED
@@ -0,0 +1 @@
1
+ @use "./compare/compare.scss";
package/src/index.tsx ADDED
@@ -0,0 +1,6 @@
1
+ import "./index.scss";
2
+
3
+ export * from "./components";
4
+ export * from "./styled";
5
+
6
+ export type * from "./types";
package/src/styled.tsx ADDED
@@ -0,0 +1,5 @@
1
+ import StyledCompare from "./compare/styled";
2
+ import StyledFilter from "./filter/styled";
3
+ import StyledHealthScore from "./health-score/styled";
4
+
5
+ export { StyledCompare, StyledHealthScore, StyledFilter };
@@ -0,0 +1,8 @@
1
+ import type { ChartLegendBaseType } from "@uniai-fe/chart-legacy";
2
+
3
+ export type CompareChartLegendCategoryDataType = {
4
+ [categoryDataKey: string]: string | ChartLegendBaseType[];
5
+ key: string;
6
+ title: string;
7
+ legend: ChartLegendBaseType[];
8
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * 템플릿; 필터 그룹 하위 데이터
3
+ * @property {string} dataKey 데이터 키
4
+ * @property {string} name 이름
5
+ */
6
+ export type FilterGroupItemDataType = {
7
+ /**
8
+ * register data key
9
+ * categoryKey 프로퍼티의 값
10
+ * - string 값이 아닌 경우, `{categoryKey}_{value}` 형태로 사용
11
+ */
12
+ value: string;
13
+ name: string;
14
+ };
15
+
16
+ /**
17
+ * 템플릿; 필터 그룹 데이터
18
+ * @property {string} key 렌더링 키
19
+ * @property {string} categoryKey 그룹 카테고리 키 (컬럼 또는 차트 레전드 데이터 키)
20
+ * @property {string} categoryName 그룹 카테고리 이름
21
+ * @property {FilterGroupItemDataType[]} items 필터그룹 하위 아이템 데이터
22
+ */
23
+ export type FilterGroupDataType = {
24
+ groupKey: string;
25
+ categoryKey: string;
26
+ categoryName: string;
27
+ items: FilterGroupItemDataType[];
28
+ };
29
+
30
+ export type FilterStateItemDataType = {
31
+ [itemKey: string]: boolean;
32
+ all: boolean;
33
+ };
34
+ export type FilterGroupStateDataType = {
35
+ [categoryKey: string]: FilterStateItemDataType;
36
+ };
37
+ export type FilterStateFormType = {
38
+ filter: FilterGroupStateDataType;
39
+ };
@@ -0,0 +1,2 @@
1
+ export type * from "./filter";
2
+ export type * from "./compare/chart";