@yeongseoksong/framework 1.2.0 → 1.3.1

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.
package/README.md CHANGED
@@ -65,6 +65,53 @@ import './env.mjs'
65
65
 
66
66
  `navItems` / `companyInfo`처럼 배열·객체인 값은 환경변수로 담을 수 없으므로 `MainLayout`에 prop으로 넘깁니다 (아래 MainLayout 항목 참고).
67
67
 
68
+ ### 3. 색상 커스터마이즈
69
+
70
+ 브랜드 색은 환경변수가 아니라 **테마 오버라이드**로 주입합니다. `theme`은 `createTheme()`이 만든 `MantineThemeOverride`라 Mantine 표준 `mergeThemeOverrides()`로 그대로 합칠 수 있습니다.
71
+
72
+ **반드시 `"use client"` 파일 안에서 합쳐야 합니다.** `ui` 번들 전체에 `"use client"`가 붙어 있어 서버 컴포넌트가 `theme`을 임포트하면 실제 객체가 아니라 client reference proxy를 받습니다. `<MantineProvider theme={theme}>`처럼 **넘기기만** 하는 건 되지만, `mergeThemeOverrides(theme, ...)`처럼 값을 **읽으면** 터집니다.
73
+
74
+ ```tsx
75
+ // app/theme.ts ← "use client" 필수
76
+ 'use client'
77
+ import { mergeThemeOverrides } from '@mantine/core'
78
+ import { theme } from '@yeongseoksong/framework/ui'
79
+
80
+ export const appTheme = mergeThemeOverrides(theme, {
81
+ colors: {
82
+ // 10단계 전부 채워야 합니다. 부분 배열은 Mantine이 허용하지 않습니다.
83
+ primary: [
84
+ '#e7f0fb', '#c8dcf4', '#a3c3ec', '#7aa8e3', '#4f8ddb',
85
+ '#2374d4', '#0b5ed7', '#094db1', '#073d8c', '#052d68',
86
+ ],
87
+ },
88
+ // 브랜드 앵커 shade를 바꾸려면 함께 지정
89
+ primaryShade: { light: 6, dark: 5 },
90
+ })
91
+ ```
92
+
93
+ ```tsx
94
+ // app/layout.tsx — Server Component 그대로 둡니다
95
+ import { MantineProvider } from '@mantine/core'
96
+ import { appTheme } from './theme'
97
+
98
+ <MantineProvider theme={appTheme} defaultColorScheme="light">
99
+ ```
100
+
101
+ 오버라이드하지 않은 키(타이포·spacing·shadows·컴포넌트 기본값)는 프레임워크 값이 그대로 유지됩니다.
102
+
103
+ **컴포넌트가 의존하는 색상 키** — 아래 키를 바꾸면 해당 컴포넌트 전체가 따라 바뀝니다. 키를 **없애면** Mantine 기본 팔레트로 폴백하므로 톤이 어긋납니다.
104
+
105
+ | 키 | 쓰이는 곳 |
106
+ | ----------- | ---------------------------------------------------------------------- |
107
+ | `primary` | `primaryColor`. 버튼·링크 hover·`SdText.Eyebrow`·`SdBadge.Primary` 등 |
108
+ | `secondary` | 보조 강조 |
109
+ | `slate` | 중립 전반 — 모든 `SdText`/`SdTitle` 본문색, 보더, 표 헤더. `dark` 별칭 |
110
+ | `red` | `SdText.Error`, `SdButton.Delete` |
111
+ | `green` | `SdButton.Excel` |
112
+
113
+ 10단계 램프를 손으로 만들기 번거로우면 [`@mantine/colors-generator`](https://mantine.dev/colors-generator/)의 `generateColors('#0b5ed7')`로 hex 하나에서 뽑을 수 있습니다(별도 설치 필요).
114
+
68
115
  ## 임포트 경로
69
116
 
70
117
  | 경로 | 내용 |
package/dist/ui/index.cjs CHANGED
@@ -1479,7 +1479,7 @@ var import_core28 = require("@mantine/core");
1479
1479
  var import_hooks = require("@mantine/hooks");
1480
1480
  var import_jsx_runtime29 = require("react/jsx-runtime");
1481
1481
  var BAR_HEIGHT = 60;
1482
- function SdHeader({ navItems, loginFlag, children }) {
1482
+ function SdHeader({ navItems, loginFlag }) {
1483
1483
  const [opened, { toggle, close }] = (0, import_hooks.useDisclosure)();
1484
1484
  const visibleItems = filterAndSort(navItems);
1485
1485
  const topItems = visibleItems.filter((item) => !item.parentId);
@@ -1532,8 +1532,7 @@ function SdHeader({ navItems, loginFlag, children }) {
1532
1532
  }) }),
1533
1533
  /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_core28.Group, { h: BAR_HEIGHT, align: "center", gap: "sm", wrap: "nowrap", children: [
1534
1534
  /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_core28.Burger, { hiddenFrom: "sm", opened, onClick: toggle }),
1535
- loginFlag && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_core28.Group, { gap: "sm", visibleFrom: "sm", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(SdButton.Primary, { size: "xs", children: "\uB85C\uADF8\uC778" }) }),
1536
- children
1535
+ loginFlag && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_core28.Group, { gap: "sm", visibleFrom: "sm", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(SdButton.Primary, { size: "xs", children: "\uB85C\uADF8\uC778" }) })
1537
1536
  ] })
1538
1537
  ] }) })
1539
1538
  }
@@ -1568,9 +1567,13 @@ var import_jsx_runtime30 = require("react/jsx-runtime");
1568
1567
  function FooterNavColumns({ items }) {
1569
1568
  const topLevel = items.filter((item) => !item.parentId);
1570
1569
  const getChildren = (parentId) => items.filter((item) => item.parentId === parentId);
1571
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_jsx_runtime30.Fragment, { children: topLevel.map((group) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_core29.Grid.Col, { span: { base: 6, sm: 4, md: "auto" }, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_core29.Stack, { gap: "sm", children: [
1570
+ const CHILD_THRESH = 4;
1571
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_jsx_runtime30.Fragment, { children: topLevel.map((group) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_core29.Grid.Col, { span: { base: 6, sm: 4, md: "auto" }, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_core29.Stack, { gap: "xs", children: [
1572
1572
  /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(SdText.Sub, { children: group.label }),
1573
- getChildren(group.id).map((link) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(SdLink.Hint, { href: link.href, children: link.label }, link.id))
1573
+ getChildren(group.id).map((link, id) => {
1574
+ if (id >= CHILD_THRESH) return;
1575
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(SdLink.Hint, { href: link.href, children: link.label }, link.id);
1576
+ })
1574
1577
  ] }) }, group.id)) });
1575
1578
  }
1576
1579
  var SOCIAL_ICONS = {
@@ -1796,10 +1799,11 @@ var import_jsx_runtime35 = require("react/jsx-runtime");
1796
1799
  function MainLayout({
1797
1800
  children,
1798
1801
  navItems,
1799
- companyInfo
1802
+ companyInfo,
1803
+ loginFlag
1800
1804
  }) {
1801
1805
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_core34.AppShell, { header: { height: 60 }, children: [
1802
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_core34.AppShell.Header, { children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(SdHeader, { navItems, loginFlag: true }) }),
1806
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_core34.AppShell.Header, { children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(SdHeader, { navItems, loginFlag }) }),
1803
1807
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_core34.AppShell.Main, { children }),
1804
1808
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(SdFooter, { company: companyInfo, navItems })
1805
1809
  ] });
@@ -2010,33 +2014,53 @@ var slate = [
2010
2014
  "#0f172a"
2011
2015
  // 9 slate-900
2012
2016
  ];
2013
- var cyan = [
2014
- "#ecfeff",
2015
- "#cffafe",
2016
- "#a5f3fc",
2017
- "#67e8f9",
2018
- "#22d3ee",
2019
- "#06b6d4",
2020
- "#0891b2",
2021
- "#0e7490",
2022
- "#155e75",
2023
- "#164e63"
2017
+ var red = [
2018
+ "#fef2f2",
2019
+ // 0 red-50
2020
+ "#fee2e2",
2021
+ // 1 red-100
2022
+ "#fecaca",
2023
+ // 2 red-200
2024
+ "#fca5a5",
2025
+ // 3 red-300
2026
+ "#f87171",
2027
+ // 4 red-400
2028
+ "#ef4444",
2029
+ // 5 red-500
2030
+ "#dc2626",
2031
+ // 6 red-600
2032
+ "#b91c1c",
2033
+ // 7 red-700
2034
+ "#991b1b",
2035
+ // 8 red-800
2036
+ "#7f1d1d"
2037
+ // 9 red-900
2024
2038
  ];
2025
- var indigo = [
2026
- "#eef2ff",
2027
- "#e0e7ff",
2028
- "#c7d2fe",
2029
- "#a5b4fc",
2030
- "#818cf8",
2031
- "#6366f1",
2032
- "#4f46e5",
2033
- "#4338ca",
2034
- "#3730a3",
2035
- "#312e81"
2039
+ var green = [
2040
+ "#f0fdf4",
2041
+ // 0 green-50
2042
+ "#dcfce7",
2043
+ // 1 green-100
2044
+ "#bbf7d0",
2045
+ // 2 green-200
2046
+ "#86efac",
2047
+ // 3 green-300
2048
+ "#4ade80",
2049
+ // 4 green-400
2050
+ "#22c55e",
2051
+ // 5 green-500
2052
+ "#16a34a",
2053
+ // 6 green-600
2054
+ "#15803d",
2055
+ // 7 green-700
2056
+ "#166534",
2057
+ // 8 green-800
2058
+ "#14532d"
2059
+ // 9 green-900
2036
2060
  ];
2037
2061
  var theme = (0, import_core36.createTheme)({
2038
2062
  /* ---- Color ---- */
2039
- colors: { primary, secondary, slate, cyan, indigo, dark: slate },
2063
+ colors: { primary, secondary, slate, red, green, dark: slate },
2040
2064
  primaryColor: "primary",
2041
2065
  primaryShade: { light: 6, dark: 5 },
2042
2066
  /* ---- Type ---- */
@@ -770,9 +770,8 @@ declare function HeroCarousel({ slides, children }: HeroCarouselProps): react_js
770
770
  interface HeaderProps {
771
771
  navItems: NavItem[];
772
772
  loginFlag?: boolean;
773
- children?: ReactNode;
774
773
  }
775
- declare function SdHeader({ navItems, loginFlag, children }: HeaderProps): react_jsx_runtime.JSX.Element;
774
+ declare function SdHeader({ navItems, loginFlag }: HeaderProps): react_jsx_runtime.JSX.Element;
776
775
 
777
776
  interface SdFooterProps {
778
777
  company: CompanyInfo;
@@ -831,10 +830,11 @@ declare const SdErrorView: {
831
830
  declare const SdErrorViewPage: typeof Page;
832
831
  declare const SdErrorViewNotFound: typeof NotFound;
833
832
 
834
- declare function MainLayout({ children, navItems, companyInfo, }: {
833
+ declare function MainLayout({ children, navItems, companyInfo, loginFlag, }: {
835
834
  children: React.ReactNode;
836
835
  navItems: NavItem[];
837
836
  companyInfo: CompanyInfo;
837
+ loginFlag?: boolean;
838
838
  }): react_jsx_runtime.JSX.Element;
839
839
 
840
840
  interface PageHeroBaseProps {
@@ -770,9 +770,8 @@ declare function HeroCarousel({ slides, children }: HeroCarouselProps): react_js
770
770
  interface HeaderProps {
771
771
  navItems: NavItem[];
772
772
  loginFlag?: boolean;
773
- children?: ReactNode;
774
773
  }
775
- declare function SdHeader({ navItems, loginFlag, children }: HeaderProps): react_jsx_runtime.JSX.Element;
774
+ declare function SdHeader({ navItems, loginFlag }: HeaderProps): react_jsx_runtime.JSX.Element;
776
775
 
777
776
  interface SdFooterProps {
778
777
  company: CompanyInfo;
@@ -831,10 +830,11 @@ declare const SdErrorView: {
831
830
  declare const SdErrorViewPage: typeof Page;
832
831
  declare const SdErrorViewNotFound: typeof NotFound;
833
832
 
834
- declare function MainLayout({ children, navItems, companyInfo, }: {
833
+ declare function MainLayout({ children, navItems, companyInfo, loginFlag, }: {
835
834
  children: React.ReactNode;
836
835
  navItems: NavItem[];
837
836
  companyInfo: CompanyInfo;
837
+ loginFlag?: boolean;
838
838
  }): react_jsx_runtime.JSX.Element;
839
839
 
840
840
  interface PageHeroBaseProps {
package/dist/ui/index.mjs CHANGED
@@ -1357,7 +1357,7 @@ import { Box as Box12, Burger, Drawer, Group as Group9, NavLink, Stack as Stack1
1357
1357
  import { useDisclosure, useFocusWithin, useHover, useMergedRef } from "@mantine/hooks";
1358
1358
  import { Fragment as Fragment4, jsx as jsx28, jsxs as jsxs16 } from "react/jsx-runtime";
1359
1359
  var BAR_HEIGHT = 60;
1360
- function SdHeader({ navItems, loginFlag, children }) {
1360
+ function SdHeader({ navItems, loginFlag }) {
1361
1361
  const [opened, { toggle, close }] = useDisclosure();
1362
1362
  const visibleItems = filterAndSort(navItems);
1363
1363
  const topItems = visibleItems.filter((item) => !item.parentId);
@@ -1410,8 +1410,7 @@ function SdHeader({ navItems, loginFlag, children }) {
1410
1410
  }) }),
1411
1411
  /* @__PURE__ */ jsxs16(Group9, { h: BAR_HEIGHT, align: "center", gap: "sm", wrap: "nowrap", children: [
1412
1412
  /* @__PURE__ */ jsx28(Burger, { hiddenFrom: "sm", opened, onClick: toggle }),
1413
- loginFlag && /* @__PURE__ */ jsx28(Group9, { gap: "sm", visibleFrom: "sm", children: /* @__PURE__ */ jsx28(SdButton.Primary, { size: "xs", children: "\uB85C\uADF8\uC778" }) }),
1414
- children
1413
+ loginFlag && /* @__PURE__ */ jsx28(Group9, { gap: "sm", visibleFrom: "sm", children: /* @__PURE__ */ jsx28(SdButton.Primary, { size: "xs", children: "\uB85C\uADF8\uC778" }) })
1415
1414
  ] })
1416
1415
  ] }) })
1417
1416
  }
@@ -1454,9 +1453,13 @@ import { Fragment as Fragment5, jsx as jsx29, jsxs as jsxs17 } from "react/jsx-r
1454
1453
  function FooterNavColumns({ items }) {
1455
1454
  const topLevel = items.filter((item) => !item.parentId);
1456
1455
  const getChildren = (parentId) => items.filter((item) => item.parentId === parentId);
1457
- return /* @__PURE__ */ jsx29(Fragment5, { children: topLevel.map((group) => /* @__PURE__ */ jsx29(Grid5.Col, { span: { base: 6, sm: 4, md: "auto" }, children: /* @__PURE__ */ jsxs17(Stack16, { gap: "sm", children: [
1456
+ const CHILD_THRESH = 4;
1457
+ return /* @__PURE__ */ jsx29(Fragment5, { children: topLevel.map((group) => /* @__PURE__ */ jsx29(Grid5.Col, { span: { base: 6, sm: 4, md: "auto" }, children: /* @__PURE__ */ jsxs17(Stack16, { gap: "xs", children: [
1458
1458
  /* @__PURE__ */ jsx29(SdText.Sub, { children: group.label }),
1459
- getChildren(group.id).map((link) => /* @__PURE__ */ jsx29(SdLink.Hint, { href: link.href, children: link.label }, link.id))
1459
+ getChildren(group.id).map((link, id) => {
1460
+ if (id >= CHILD_THRESH) return;
1461
+ return /* @__PURE__ */ jsx29(SdLink.Hint, { href: link.href, children: link.label }, link.id);
1462
+ })
1460
1463
  ] }) }, group.id)) });
1461
1464
  }
1462
1465
  var SOCIAL_ICONS = {
@@ -1682,10 +1685,11 @@ import { jsx as jsx34, jsxs as jsxs22 } from "react/jsx-runtime";
1682
1685
  function MainLayout({
1683
1686
  children,
1684
1687
  navItems,
1685
- companyInfo
1688
+ companyInfo,
1689
+ loginFlag
1686
1690
  }) {
1687
1691
  return /* @__PURE__ */ jsxs22(AppShell, { header: { height: 60 }, children: [
1688
- /* @__PURE__ */ jsx34(AppShell.Header, { children: /* @__PURE__ */ jsx34(SdHeader, { navItems, loginFlag: true }) }),
1692
+ /* @__PURE__ */ jsx34(AppShell.Header, { children: /* @__PURE__ */ jsx34(SdHeader, { navItems, loginFlag }) }),
1689
1693
  /* @__PURE__ */ jsx34(AppShell.Main, { children }),
1690
1694
  /* @__PURE__ */ jsx34(SdFooter, { company: companyInfo, navItems })
1691
1695
  ] });
@@ -1896,33 +1900,53 @@ var slate = [
1896
1900
  "#0f172a"
1897
1901
  // 9 slate-900
1898
1902
  ];
1899
- var cyan = [
1900
- "#ecfeff",
1901
- "#cffafe",
1902
- "#a5f3fc",
1903
- "#67e8f9",
1904
- "#22d3ee",
1905
- "#06b6d4",
1906
- "#0891b2",
1907
- "#0e7490",
1908
- "#155e75",
1909
- "#164e63"
1903
+ var red = [
1904
+ "#fef2f2",
1905
+ // 0 red-50
1906
+ "#fee2e2",
1907
+ // 1 red-100
1908
+ "#fecaca",
1909
+ // 2 red-200
1910
+ "#fca5a5",
1911
+ // 3 red-300
1912
+ "#f87171",
1913
+ // 4 red-400
1914
+ "#ef4444",
1915
+ // 5 red-500
1916
+ "#dc2626",
1917
+ // 6 red-600
1918
+ "#b91c1c",
1919
+ // 7 red-700
1920
+ "#991b1b",
1921
+ // 8 red-800
1922
+ "#7f1d1d"
1923
+ // 9 red-900
1910
1924
  ];
1911
- var indigo = [
1912
- "#eef2ff",
1913
- "#e0e7ff",
1914
- "#c7d2fe",
1915
- "#a5b4fc",
1916
- "#818cf8",
1917
- "#6366f1",
1918
- "#4f46e5",
1919
- "#4338ca",
1920
- "#3730a3",
1921
- "#312e81"
1925
+ var green = [
1926
+ "#f0fdf4",
1927
+ // 0 green-50
1928
+ "#dcfce7",
1929
+ // 1 green-100
1930
+ "#bbf7d0",
1931
+ // 2 green-200
1932
+ "#86efac",
1933
+ // 3 green-300
1934
+ "#4ade80",
1935
+ // 4 green-400
1936
+ "#22c55e",
1937
+ // 5 green-500
1938
+ "#16a34a",
1939
+ // 6 green-600
1940
+ "#15803d",
1941
+ // 7 green-700
1942
+ "#166534",
1943
+ // 8 green-800
1944
+ "#14532d"
1945
+ // 9 green-900
1922
1946
  ];
1923
1947
  var theme = createTheme({
1924
1948
  /* ---- Color ---- */
1925
- colors: { primary, secondary, slate, cyan, indigo, dark: slate },
1949
+ colors: { primary, secondary, slate, red, green, dark: slate },
1926
1950
  primaryColor: "primary",
1927
1951
  primaryShade: { light: 6, dark: 5 },
1928
1952
  /* ---- Type ---- */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeongseoksong/framework",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "files": [
5
5
  "dist"
6
6
  ],