sh-ui-cli 0.85.1 → 0.86.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.
Files changed (50) hide show
  1. package/data/changelog/versions.json +24 -0
  2. package/package.json +1 -1
  3. package/src/constants.js +1 -1
  4. package/src/create/architectures/archSchema.js +1 -1
  5. package/src/create/architectures/flat.js +1 -1
  6. package/src/create/architectures/fsd.js +1 -1
  7. package/src/create/describeTemplate.js +21 -2
  8. package/src/create/generator.js +86 -3
  9. package/src/create/templateManifest.js +49 -0
  10. package/src/mcp.mjs +4 -4
  11. package/templates/nextjs-standalone/package.json +1 -0
  12. package/templates/vite-standalone/CLAUDE.md +7 -0
  13. package/templates/vite-standalone/README.md +23 -0
  14. package/templates/vite-standalone/_arch/flat/sh-ui.config.json +22 -0
  15. package/templates/vite-standalone/_arch/flat/src/App.tsx +13 -0
  16. package/templates/vite-standalone/_arch/flat/src/components/layouts/RootLayout.tsx +5 -0
  17. package/templates/vite-standalone/_arch/flat/src/components/providers/GlobalProvider/index.tsx +13 -0
  18. package/templates/vite-standalone/_arch/flat/src/components/providers/index.tsx +1 -0
  19. package/templates/vite-standalone/_arch/flat/src/components/providers/theme/ThemeProvider.tsx +59 -0
  20. package/templates/vite-standalone/_arch/flat/src/lib/api/queryClient.ts +12 -0
  21. package/templates/vite-standalone/_arch/flat/src/lib/hooks/useTheme.ts +8 -0
  22. package/templates/vite-standalone/_arch/flat/src/lib/styles/globals.css +35 -0
  23. package/templates/vite-standalone/_arch/flat/src/lib/styles/tokens.css +203 -0
  24. package/templates/vite-standalone/_arch/flat/src/lib/utils/utils.ts +6 -0
  25. package/templates/vite-standalone/_arch/flat/src/main.tsx +10 -0
  26. package/templates/vite-standalone/_arch/flat/tsconfig.app.json +23 -0
  27. package/templates/vite-standalone/_arch/fsd/sh-ui.config.json +22 -0
  28. package/templates/vite-standalone/_arch/fsd/src/App.tsx +13 -0
  29. package/templates/vite-standalone/_arch/fsd/src/app/layouts/RootLayout.tsx +5 -0
  30. package/templates/vite-standalone/_arch/fsd/src/app/providers/GlobalProvider/index.tsx +13 -0
  31. package/templates/vite-standalone/_arch/fsd/src/app/providers/theme/ThemeProvider.tsx +59 -0
  32. package/templates/vite-standalone/_arch/fsd/src/main.tsx +10 -0
  33. package/templates/vite-standalone/_arch/fsd/src/shared/api/queryClient.ts +12 -0
  34. package/templates/vite-standalone/_arch/fsd/src/shared/hooks/useTheme.ts +8 -0
  35. package/templates/vite-standalone/_arch/fsd/src/shared/lib/utils.ts +6 -0
  36. package/templates/vite-standalone/_arch/fsd/src/shared/styles/globals.css +35 -0
  37. package/templates/vite-standalone/_arch/fsd/src/shared/styles/tokens.css +203 -0
  38. package/templates/vite-standalone/_arch/fsd/tsconfig.app.json +22 -0
  39. package/templates/vite-standalone/eslint.config.js +34 -0
  40. package/templates/vite-standalone/gitignore +8 -0
  41. package/templates/vite-standalone/index.html +22 -0
  42. package/templates/vite-standalone/package.json +64 -0
  43. package/templates/vite-standalone/src/App.tsx +5 -0
  44. package/templates/vite-standalone/src/Home.tsx +7 -0
  45. package/templates/vite-standalone/src/main.tsx +9 -0
  46. package/templates/vite-standalone/tsconfig.json +7 -0
  47. package/templates/vite-standalone/tsconfig.node.json +12 -0
  48. package/templates/vite-standalone/vite.config.ts +11 -0
  49. package/templates/vite-standalone/vitest.config.ts +13 -0
  50. package/templates/vite-standalone/vitest.setup.ts +1 -0
@@ -0,0 +1,203 @@
1
+ /* Generated by @sh-ui/tokens — do not edit directly */
2
+ /* base=neutral radius=md mode=light-dark */
3
+
4
+ /* sh-ui:theme-colors-start */
5
+ :root {
6
+ --background: #FFFFFF;
7
+ --background-subtle: #FAFAFA;
8
+ --background-muted: #F5F5F5;
9
+ --background-inverse: #0A0A0A;
10
+ --foreground: #0A0A0A;
11
+ --foreground-muted: #525252;
12
+ --foreground-subtle: #A3A3A3;
13
+ --foreground-inverse: #FFFFFF;
14
+ --border: #E5E5E5;
15
+ --border-strong: #D4D4D4;
16
+ --primary: #171717;
17
+ --primary-foreground: #FAFAFA;
18
+ --primary-hover: #262626;
19
+ --ring: color-mix(in srgb, var(--primary) 50%, transparent);
20
+ --danger: #DC2626;
21
+ --danger-hover: color-mix(in srgb, var(--danger) 90%, black);
22
+ --danger-foreground: #FFFFFF;
23
+ --success: #16A34A;
24
+ --success-foreground: #FFFFFF;
25
+ --warning: #D97706;
26
+ --warning-foreground: #FFFFFF;
27
+ --info: #2563EB;
28
+ --info-foreground: #FFFFFF;
29
+ --sidebar-bg: #FAFAFA;
30
+ --sidebar-fg: #0A0A0A;
31
+ --sidebar-border: #E5E5E5;
32
+ --sidebar-accent: #F5F5F5;
33
+ --sidebar-accent-fg: #0A0A0A;
34
+ }
35
+ @media (prefers-color-scheme: dark) {
36
+ :root:not(.light):not(.dark) {
37
+ --background: #0A0A0A;
38
+ --background-subtle: #171717;
39
+ --background-muted: #262626;
40
+ --background-inverse: #FFFFFF;
41
+ --foreground: #FAFAFA;
42
+ --foreground-muted: #A3A3A3;
43
+ --foreground-subtle: #737373;
44
+ --foreground-inverse: #0A0A0A;
45
+ --border: #262626;
46
+ --border-strong: #404040;
47
+ --primary: #FAFAFA;
48
+ --primary-foreground: #171717;
49
+ --primary-hover: #E5E5E5;
50
+ --danger: #DC2626;
51
+ --danger-hover: color-mix(in srgb, var(--danger) 90%, black);
52
+ --danger-foreground: #FFFFFF;
53
+ --success: #22C55E;
54
+ --success-foreground: #052E16;
55
+ --warning: #F59E0B;
56
+ --warning-foreground: #451A03;
57
+ --info: #3B82F6;
58
+ --info-foreground: #172554;
59
+ --sidebar-bg: #171717;
60
+ --sidebar-fg: #FAFAFA;
61
+ --sidebar-border: #262626;
62
+ --sidebar-accent: #262626;
63
+ --sidebar-accent-fg: #FAFAFA;
64
+ }
65
+ }
66
+ .dark {
67
+ --background: #0A0A0A;
68
+ --background-subtle: #171717;
69
+ --background-muted: #262626;
70
+ --background-inverse: #FFFFFF;
71
+ --foreground: #FAFAFA;
72
+ --foreground-muted: #A3A3A3;
73
+ --foreground-subtle: #737373;
74
+ --foreground-inverse: #0A0A0A;
75
+ --border: #262626;
76
+ --border-strong: #404040;
77
+ --primary: #FAFAFA;
78
+ --primary-foreground: #171717;
79
+ --primary-hover: #E5E5E5;
80
+ --danger: #DC2626;
81
+ --danger-hover: color-mix(in srgb, var(--danger) 90%, black);
82
+ --danger-foreground: #FFFFFF;
83
+ --success: #22C55E;
84
+ --success-foreground: #052E16;
85
+ --warning: #F59E0B;
86
+ --warning-foreground: #451A03;
87
+ --info: #3B82F6;
88
+ --info-foreground: #172554;
89
+ --sidebar-bg: #171717;
90
+ --sidebar-fg: #FAFAFA;
91
+ --sidebar-border: #262626;
92
+ --sidebar-accent: #262626;
93
+ --sidebar-accent-fg: #FAFAFA;
94
+ }
95
+ /* sh-ui:theme-colors-end */
96
+
97
+ :root {
98
+ /* sh-ui:theme-radius-start */
99
+ --radius: 0.5rem;
100
+ /* sh-ui:theme-radius-end */
101
+ /* sh-ui:theme-space-start */
102
+ --space-0: 0;
103
+ --space-1: 0.25rem;
104
+ --space-2: 0.5rem;
105
+ --space-3: 0.75rem;
106
+ --space-4: 1rem;
107
+ --space-5: 1.25rem;
108
+ --space-6: 1.5rem;
109
+ --space-8: 2rem;
110
+ --space-10: 2.5rem;
111
+ --space-12: 3rem;
112
+ --space-16: 4rem;
113
+ /* sh-ui:theme-space-end */
114
+ /* sh-ui:theme-text-start */
115
+ --text-xs: 0.75rem;
116
+ --text-sm: 0.875rem;
117
+ --text-base: 1rem;
118
+ --text-lg: 1.125rem;
119
+ --text-xl: 1.25rem;
120
+ --text-2xl: 1.5rem;
121
+ --text-3xl: 1.875rem;
122
+ --text-4xl: 2.25rem;
123
+ /* sh-ui:theme-text-end */
124
+ /* sh-ui:theme-weight-start */
125
+ --weight-regular: 400;
126
+ --weight-medium: 500;
127
+ --weight-semibold: 600;
128
+ --weight-bold: 700;
129
+ /* sh-ui:theme-weight-end */
130
+ /* sh-ui:theme-shadow-start */
131
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.08);
132
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.12);
133
+ --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.15);
134
+ --shadow-xl: 0 16px 48px rgba(0, 0, 0, 0.18);
135
+ --shadow-menu: 0 4px 6px -1px rgba(0, 0, 0, 0.08), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
136
+ /* sh-ui:theme-shadow-end */
137
+ /* sh-ui:theme-duration-start */
138
+ --duration-fast: 120ms;
139
+ --duration-base: 160ms;
140
+ --duration-slow: 200ms;
141
+ /* sh-ui:theme-duration-end */
142
+ /* sh-ui:theme-ease-start */
143
+ --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
144
+ --ease-emphasized: cubic-bezier(0.2, 0, 0, 1);
145
+ /* sh-ui:theme-ease-end */
146
+ /* sh-ui:theme-control-start */
147
+ --control-sm: 2rem;
148
+ --control-md: 2.25rem;
149
+ --control-lg: 2.5rem;
150
+ /* sh-ui:theme-control-end */
151
+ /* sh-ui:theme-border-width-start */
152
+ --border-width: 1px;
153
+ --border-width-strong: 2px;
154
+ /* sh-ui:theme-border-width-end */
155
+ /* sh-ui:theme-gradient-start */
156
+ --gradient-primary: linear-gradient(135deg, #171717 0%, #525252 100%);
157
+ --gradient-surface: linear-gradient(180deg, #FFFFFF 0%, #F5F5F5 100%);
158
+ --gradient-overlay: linear-gradient(180deg, #000000 0%, #1F1F1F 100%);
159
+ /* sh-ui:theme-gradient-end */
160
+ --opacity-disabled: 0.5;
161
+ --z-base: 0;
162
+ --z-sticky: 100;
163
+ --z-dropdown: 200;
164
+ --z-overlay: 300;
165
+ --z-modal: 400;
166
+ --z-popover: 500;
167
+ --z-toast: 600;
168
+ --z-tooltip: 700;
169
+ --bp-sm: 640px;
170
+ --bp-md: 768px;
171
+ --bp-lg: 1024px;
172
+ --bp-xl: 1280px;
173
+ }
174
+
175
+ /* WCAG 2.5.5 — 터치 타겟 최소 44×44px. 마우스/스타일러스 대신 손가락 입력 시 control 높이를 보정. */
176
+ @media (hover: none) and (pointer: coarse) {
177
+ :root {
178
+ --control-sm: 2.75rem;
179
+ --control-md: 2.75rem;
180
+ }
181
+ }
182
+
183
+ /* WCAG AA 보장 — 사용자가 OS/브라우저에서 "고대비" 접근성 옵션 켰을 때.
184
+ * foreground-subtle, border, border-strong 만 강화 (다른 토큰은 이미 AA 통과). */
185
+ @media (prefers-contrast: high) {
186
+ :root {
187
+ --foreground-subtle: #767676;
188
+ --border: #767676;
189
+ --border-strong: #595959;
190
+ }
191
+ .dark {
192
+ --foreground-subtle: #B3B3B3;
193
+ --border: #757575;
194
+ --border-strong: #999999;
195
+ }
196
+ @media (prefers-color-scheme: dark) {
197
+ :root:not(.light):not(.dark) {
198
+ --foreground-subtle: #B3B3B3;
199
+ --border: #757575;
200
+ --border-strong: #999999;
201
+ }
202
+ }
203
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import App from './App';
4
+ import './lib/styles/globals.css';
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "target": "ES2022",
5
+ "lib": ["es2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "moduleResolution": "Bundler",
8
+ "jsx": "react-jsx",
9
+ "strict": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "isolatedModules": true,
13
+ "resolveJsonModule": true,
14
+ "allowSyntheticDefaultImports": true,
15
+ "noEmit": true,
16
+ "baseUrl": ".",
17
+ "paths": {
18
+ "@/lib/*": ["./src/lib/*"],
19
+ "@/components/*": ["./src/components/*"]
20
+ }
21
+ },
22
+ "include": ["src"]
23
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/sanghyeonKim0201/sh-ui/live/packages/cli/sh-ui.schema.json",
3
+ "platform": "react",
4
+ "cssFramework": "tailwind",
5
+ "theme": {
6
+ "base": "neutral",
7
+ "radius": "md",
8
+ "mode": "light-dark"
9
+ },
10
+ "paths": {
11
+ "tokens": "src/shared/styles/tokens.css",
12
+ "cssEntry": "src/shared/styles/globals.css",
13
+ "styles": "src/shared/styles",
14
+ "components": "src/shared/ui",
15
+ "utils": "src/shared/lib/utils.ts"
16
+ },
17
+ "aliases": {
18
+ "components": "@/shared/ui",
19
+ "utils": "@/shared/lib/utils",
20
+ "ui": "@/shared/ui"
21
+ }
22
+ }
@@ -0,0 +1,13 @@
1
+ import { GlobalProvider } from '@/app/providers/GlobalProvider';
2
+ import { RootLayout } from '@/app/layouts/RootLayout';
3
+ import Home from './Home';
4
+
5
+ export default function App() {
6
+ return (
7
+ <GlobalProvider>
8
+ <RootLayout>
9
+ <Home />
10
+ </RootLayout>
11
+ </GlobalProvider>
12
+ );
13
+ }
@@ -0,0 +1,5 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export function RootLayout({ children }: { children: ReactNode }) {
4
+ return <div className="min-h-screen bg-background text-foreground">{children}</div>;
5
+ }
@@ -0,0 +1,13 @@
1
+ import { QueryClientProvider } from '@tanstack/react-query';
2
+ import { type ReactNode, useState } from 'react';
3
+ import { createQueryClient } from '@/shared/api/queryClient';
4
+ import { ThemeProvider } from '../theme/ThemeProvider';
5
+
6
+ export function GlobalProvider({ children }: { children: ReactNode }) {
7
+ const [queryClient] = useState(() => createQueryClient());
8
+ return (
9
+ <ThemeProvider>
10
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
11
+ </ThemeProvider>
12
+ );
13
+ }
@@ -0,0 +1,59 @@
1
+ import { createContext, useCallback, useEffect, useMemo, useState, type ReactNode } from 'react';
2
+
3
+ export type Theme = 'light' | 'dark' | 'system';
4
+
5
+ type ThemeContextValue = {
6
+ theme: Theme;
7
+ resolvedTheme: 'light' | 'dark';
8
+ setTheme: (theme: Theme) => void;
9
+ };
10
+
11
+ export const ThemeContext = createContext<ThemeContextValue | null>(null);
12
+
13
+ const STORAGE_KEY = 'theme';
14
+
15
+ function getSystemTheme(): 'light' | 'dark' {
16
+ if (typeof window === 'undefined') return 'light';
17
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
18
+ }
19
+
20
+ function resolveTheme(theme: Theme): 'light' | 'dark' {
21
+ return theme === 'system' ? getSystemTheme() : theme;
22
+ }
23
+
24
+ export function ThemeProvider({ children }: { children: ReactNode }) {
25
+ const [theme, setThemeState] = useState<Theme>(() => {
26
+ if (typeof window === 'undefined') return 'system';
27
+ return (localStorage.getItem(STORAGE_KEY) as Theme) ?? 'system';
28
+ });
29
+
30
+ const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>(() => resolveTheme(theme));
31
+
32
+ useEffect(() => {
33
+ const resolved = resolveTheme(theme);
34
+ setResolvedTheme(resolved);
35
+ document.documentElement.classList.toggle('dark', resolved === 'dark');
36
+ }, [theme]);
37
+
38
+ useEffect(() => {
39
+ if (theme !== 'system') return;
40
+ const mq = window.matchMedia('(prefers-color-scheme: dark)');
41
+ const handler = () => {
42
+ const resolved = getSystemTheme();
43
+ setResolvedTheme(resolved);
44
+ document.documentElement.classList.toggle('dark', resolved === 'dark');
45
+ };
46
+ mq.addEventListener('change', handler);
47
+ return () => mq.removeEventListener('change', handler);
48
+ }, [theme]);
49
+
50
+ const setTheme = useCallback((next: Theme) => {
51
+ setThemeState(next);
52
+ if (next === 'system') localStorage.removeItem(STORAGE_KEY);
53
+ else localStorage.setItem(STORAGE_KEY, next);
54
+ }, []);
55
+
56
+ const value = useMemo(() => ({ theme, resolvedTheme, setTheme }), [theme, resolvedTheme, setTheme]);
57
+
58
+ return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
59
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import App from './App';
4
+ import '@/shared/styles/globals.css';
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );
@@ -0,0 +1,12 @@
1
+ import { QueryClient } from '@tanstack/react-query';
2
+
3
+ export function createQueryClient() {
4
+ return new QueryClient({
5
+ defaultOptions: {
6
+ queries: {
7
+ staleTime: 60 * 1000,
8
+ refetchOnWindowFocus: false,
9
+ },
10
+ },
11
+ });
12
+ }
@@ -0,0 +1,8 @@
1
+ import { useContext } from 'react';
2
+ import { ThemeContext } from '@/app/providers/theme/ThemeProvider';
3
+
4
+ export function useTheme() {
5
+ const ctx = useContext(ThemeContext);
6
+ if (!ctx) throw new Error('useTheme must be used within a ThemeProvider');
7
+ return ctx;
8
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,35 @@
1
+ /* sh-ui:external-imports-start
2
+ * 외부 폰트 / 아이콘셋 / 디자인시스템 URL @import 는 반드시 이 블록 안에 둘 것. */
3
+ /* sh-ui:external-imports-end */
4
+
5
+ @import 'tailwindcss';
6
+ @import './tokens.css';
7
+
8
+ @custom-variant dark (&:is(.dark *));
9
+
10
+ @theme inline {
11
+ --color-background: var(--background);
12
+ --color-background-subtle: var(--background-subtle);
13
+ --color-background-muted: var(--background-muted);
14
+ --color-background-inverse: var(--background-inverse);
15
+ --color-foreground: var(--foreground);
16
+ --color-foreground-muted: var(--foreground-muted);
17
+ --color-foreground-subtle: var(--foreground-subtle);
18
+ --color-foreground-inverse: var(--foreground-inverse);
19
+ --color-border: var(--border);
20
+ --color-border-strong: var(--border-strong);
21
+ --color-primary: var(--primary);
22
+ --color-primary-foreground: var(--primary-foreground);
23
+ --color-primary-hover: var(--primary-hover);
24
+ --color-ring: var(--ring);
25
+ --color-danger: var(--danger);
26
+ --color-danger-hover: var(--danger-hover);
27
+ --color-danger-foreground: var(--danger-foreground);
28
+ --color-success: var(--success);
29
+ --color-success-foreground: var(--success-foreground);
30
+ --color-warning: var(--warning);
31
+ --color-warning-foreground: var(--warning-foreground);
32
+ --color-info: var(--info);
33
+ --color-info-foreground: var(--info-foreground);
34
+ --color-sidebar-bg: var(--sidebar-bg);
35
+ }
@@ -0,0 +1,203 @@
1
+ /* Generated by @sh-ui/tokens — do not edit directly */
2
+ /* base=neutral radius=md mode=light-dark */
3
+
4
+ /* sh-ui:theme-colors-start */
5
+ :root {
6
+ --background: #FFFFFF;
7
+ --background-subtle: #FAFAFA;
8
+ --background-muted: #F5F5F5;
9
+ --background-inverse: #0A0A0A;
10
+ --foreground: #0A0A0A;
11
+ --foreground-muted: #525252;
12
+ --foreground-subtle: #A3A3A3;
13
+ --foreground-inverse: #FFFFFF;
14
+ --border: #E5E5E5;
15
+ --border-strong: #D4D4D4;
16
+ --primary: #171717;
17
+ --primary-foreground: #FAFAFA;
18
+ --primary-hover: #262626;
19
+ --ring: color-mix(in srgb, var(--primary) 50%, transparent);
20
+ --danger: #DC2626;
21
+ --danger-hover: color-mix(in srgb, var(--danger) 90%, black);
22
+ --danger-foreground: #FFFFFF;
23
+ --success: #16A34A;
24
+ --success-foreground: #FFFFFF;
25
+ --warning: #D97706;
26
+ --warning-foreground: #FFFFFF;
27
+ --info: #2563EB;
28
+ --info-foreground: #FFFFFF;
29
+ --sidebar-bg: #FAFAFA;
30
+ --sidebar-fg: #0A0A0A;
31
+ --sidebar-border: #E5E5E5;
32
+ --sidebar-accent: #F5F5F5;
33
+ --sidebar-accent-fg: #0A0A0A;
34
+ }
35
+ @media (prefers-color-scheme: dark) {
36
+ :root:not(.light):not(.dark) {
37
+ --background: #0A0A0A;
38
+ --background-subtle: #171717;
39
+ --background-muted: #262626;
40
+ --background-inverse: #FFFFFF;
41
+ --foreground: #FAFAFA;
42
+ --foreground-muted: #A3A3A3;
43
+ --foreground-subtle: #737373;
44
+ --foreground-inverse: #0A0A0A;
45
+ --border: #262626;
46
+ --border-strong: #404040;
47
+ --primary: #FAFAFA;
48
+ --primary-foreground: #171717;
49
+ --primary-hover: #E5E5E5;
50
+ --danger: #DC2626;
51
+ --danger-hover: color-mix(in srgb, var(--danger) 90%, black);
52
+ --danger-foreground: #FFFFFF;
53
+ --success: #22C55E;
54
+ --success-foreground: #052E16;
55
+ --warning: #F59E0B;
56
+ --warning-foreground: #451A03;
57
+ --info: #3B82F6;
58
+ --info-foreground: #172554;
59
+ --sidebar-bg: #171717;
60
+ --sidebar-fg: #FAFAFA;
61
+ --sidebar-border: #262626;
62
+ --sidebar-accent: #262626;
63
+ --sidebar-accent-fg: #FAFAFA;
64
+ }
65
+ }
66
+ .dark {
67
+ --background: #0A0A0A;
68
+ --background-subtle: #171717;
69
+ --background-muted: #262626;
70
+ --background-inverse: #FFFFFF;
71
+ --foreground: #FAFAFA;
72
+ --foreground-muted: #A3A3A3;
73
+ --foreground-subtle: #737373;
74
+ --foreground-inverse: #0A0A0A;
75
+ --border: #262626;
76
+ --border-strong: #404040;
77
+ --primary: #FAFAFA;
78
+ --primary-foreground: #171717;
79
+ --primary-hover: #E5E5E5;
80
+ --danger: #DC2626;
81
+ --danger-hover: color-mix(in srgb, var(--danger) 90%, black);
82
+ --danger-foreground: #FFFFFF;
83
+ --success: #22C55E;
84
+ --success-foreground: #052E16;
85
+ --warning: #F59E0B;
86
+ --warning-foreground: #451A03;
87
+ --info: #3B82F6;
88
+ --info-foreground: #172554;
89
+ --sidebar-bg: #171717;
90
+ --sidebar-fg: #FAFAFA;
91
+ --sidebar-border: #262626;
92
+ --sidebar-accent: #262626;
93
+ --sidebar-accent-fg: #FAFAFA;
94
+ }
95
+ /* sh-ui:theme-colors-end */
96
+
97
+ :root {
98
+ /* sh-ui:theme-radius-start */
99
+ --radius: 0.5rem;
100
+ /* sh-ui:theme-radius-end */
101
+ /* sh-ui:theme-space-start */
102
+ --space-0: 0;
103
+ --space-1: 0.25rem;
104
+ --space-2: 0.5rem;
105
+ --space-3: 0.75rem;
106
+ --space-4: 1rem;
107
+ --space-5: 1.25rem;
108
+ --space-6: 1.5rem;
109
+ --space-8: 2rem;
110
+ --space-10: 2.5rem;
111
+ --space-12: 3rem;
112
+ --space-16: 4rem;
113
+ /* sh-ui:theme-space-end */
114
+ /* sh-ui:theme-text-start */
115
+ --text-xs: 0.75rem;
116
+ --text-sm: 0.875rem;
117
+ --text-base: 1rem;
118
+ --text-lg: 1.125rem;
119
+ --text-xl: 1.25rem;
120
+ --text-2xl: 1.5rem;
121
+ --text-3xl: 1.875rem;
122
+ --text-4xl: 2.25rem;
123
+ /* sh-ui:theme-text-end */
124
+ /* sh-ui:theme-weight-start */
125
+ --weight-regular: 400;
126
+ --weight-medium: 500;
127
+ --weight-semibold: 600;
128
+ --weight-bold: 700;
129
+ /* sh-ui:theme-weight-end */
130
+ /* sh-ui:theme-shadow-start */
131
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.08);
132
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.12);
133
+ --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.15);
134
+ --shadow-xl: 0 16px 48px rgba(0, 0, 0, 0.18);
135
+ --shadow-menu: 0 4px 6px -1px rgba(0, 0, 0, 0.08), 0 2px 4px -2px rgba(0, 0, 0, 0.05);
136
+ /* sh-ui:theme-shadow-end */
137
+ /* sh-ui:theme-duration-start */
138
+ --duration-fast: 120ms;
139
+ --duration-base: 160ms;
140
+ --duration-slow: 200ms;
141
+ /* sh-ui:theme-duration-end */
142
+ /* sh-ui:theme-ease-start */
143
+ --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
144
+ --ease-emphasized: cubic-bezier(0.2, 0, 0, 1);
145
+ /* sh-ui:theme-ease-end */
146
+ /* sh-ui:theme-control-start */
147
+ --control-sm: 2rem;
148
+ --control-md: 2.25rem;
149
+ --control-lg: 2.5rem;
150
+ /* sh-ui:theme-control-end */
151
+ /* sh-ui:theme-border-width-start */
152
+ --border-width: 1px;
153
+ --border-width-strong: 2px;
154
+ /* sh-ui:theme-border-width-end */
155
+ /* sh-ui:theme-gradient-start */
156
+ --gradient-primary: linear-gradient(135deg, #171717 0%, #525252 100%);
157
+ --gradient-surface: linear-gradient(180deg, #FFFFFF 0%, #F5F5F5 100%);
158
+ --gradient-overlay: linear-gradient(180deg, #000000 0%, #1F1F1F 100%);
159
+ /* sh-ui:theme-gradient-end */
160
+ --opacity-disabled: 0.5;
161
+ --z-base: 0;
162
+ --z-sticky: 100;
163
+ --z-dropdown: 200;
164
+ --z-overlay: 300;
165
+ --z-modal: 400;
166
+ --z-popover: 500;
167
+ --z-toast: 600;
168
+ --z-tooltip: 700;
169
+ --bp-sm: 640px;
170
+ --bp-md: 768px;
171
+ --bp-lg: 1024px;
172
+ --bp-xl: 1280px;
173
+ }
174
+
175
+ /* WCAG 2.5.5 — 터치 타겟 최소 44×44px. 마우스/스타일러스 대신 손가락 입력 시 control 높이를 보정. */
176
+ @media (hover: none) and (pointer: coarse) {
177
+ :root {
178
+ --control-sm: 2.75rem;
179
+ --control-md: 2.75rem;
180
+ }
181
+ }
182
+
183
+ /* WCAG AA 보장 — 사용자가 OS/브라우저에서 "고대비" 접근성 옵션 켰을 때.
184
+ * foreground-subtle, border, border-strong 만 강화 (다른 토큰은 이미 AA 통과). */
185
+ @media (prefers-contrast: high) {
186
+ :root {
187
+ --foreground-subtle: #767676;
188
+ --border: #767676;
189
+ --border-strong: #595959;
190
+ }
191
+ .dark {
192
+ --foreground-subtle: #B3B3B3;
193
+ --border: #757575;
194
+ --border-strong: #999999;
195
+ }
196
+ @media (prefers-color-scheme: dark) {
197
+ :root:not(.light):not(.dark) {
198
+ --foreground-subtle: #B3B3B3;
199
+ --border: #757575;
200
+ --border-strong: #999999;
201
+ }
202
+ }
203
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "target": "ES2022",
5
+ "lib": ["es2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "moduleResolution": "Bundler",
8
+ "jsx": "react-jsx",
9
+ "strict": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "isolatedModules": true,
13
+ "resolveJsonModule": true,
14
+ "allowSyntheticDefaultImports": true,
15
+ "noEmit": true,
16
+ "baseUrl": ".",
17
+ "paths": {
18
+ "@/*": ["./src/*"]
19
+ }
20
+ },
21
+ "include": ["src"]
22
+ }