@xfilecom/xframe 0.1.18 → 0.1.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfilecom/xframe",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Scaffold full-stack app: Nest + @xfilecom/backend-core, Vite/React + @xfilecom/front-core",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {
@@ -1,7 +1,9 @@
1
1
  import React from 'react';
2
2
  import ReactDOM from 'react-dom/client';
3
3
  import '@xfilecom/front-core/tokens.css';
4
+ import '../../shared/src/styles/xfc-theme.css';
4
5
  import '@xfilecom/front-core/base.css';
6
+ import '../../shared/src/styles/app.css';
5
7
  import { App } from './App';
6
8
 
7
9
  ReactDOM.createRoot(document.getElementById('root')!).render(
@@ -1,18 +1,24 @@
1
- import { Badge, Text } from '@xfilecom/front-core';
1
+ import { useState } from 'react';
2
+ import { Badge, Button, Stack, Text } from '@xfilecom/front-core';
2
3
  import {
3
4
  Shell,
4
5
  useHealthStatus,
5
6
  FALLBACK_API_BASE_URL,
6
7
  } from '__WEB_SHARED_WORKSPACE__';
8
+ import { FrontCoreShowcase } from './FrontCoreShowcase';
7
9
 
8
10
  const apiBase = import.meta.env.VITE_API_BASE_URL || FALLBACK_API_BASE_URL;
9
11
  const title = import.meta.env.VITE_APP_TITLE || '__PACKAGE_NAME__';
10
12
 
13
+ type Tab = 'api' | 'atoms';
14
+
11
15
  export function App() {
16
+ const [tab, setTab] = useState<Tab>('atoms');
12
17
  const { text, error } = useHealthStatus(apiBase);
13
18
 
14
19
  return (
15
20
  <Shell
21
+ wide={tab === 'atoms'}
16
22
  title={title}
17
23
  subtitle={
18
24
  <>
@@ -23,15 +29,36 @@ export function App() {
23
29
  </>
24
30
  }
25
31
  >
26
- {error ? (
27
- <Text as="pre" variant="body" style={{ color: 'var(--xfc-warning)', margin: 0, whiteSpace: 'pre-wrap' }}>
28
- {error}
29
- </Text>
30
- ) : (
31
- <Text as="pre" variant="small" style={{ margin: 0, overflow: 'auto' }}>
32
- {text}
33
- </Text>
34
- )}
32
+ <Stack direction="column" gap="md" align="stretch">
33
+ <Stack direction="row" gap="sm" align="center" style={{ flexWrap: 'wrap' }}>
34
+ <Button
35
+ type="button"
36
+ variant={tab === 'atoms' ? 'primary' : 'secondary'}
37
+ onClick={() => setTab('atoms')}
38
+ >
39
+ Atoms 예시
40
+ </Button>
41
+ <Button
42
+ type="button"
43
+ variant={tab === 'api' ? 'primary' : 'secondary'}
44
+ onClick={() => setTab('api')}
45
+ >
46
+ API health
47
+ </Button>
48
+ </Stack>
49
+
50
+ {tab === 'atoms' ? (
51
+ <FrontCoreShowcase />
52
+ ) : error ? (
53
+ <Text as="pre" variant="body" style={{ color: 'var(--xfc-warning)', margin: 0, whiteSpace: 'pre-wrap' }}>
54
+ {error}
55
+ </Text>
56
+ ) : (
57
+ <Text as="pre" variant="small" style={{ margin: 0, overflow: 'auto' }}>
58
+ {text}
59
+ </Text>
60
+ )}
61
+ </Stack>
35
62
  </Shell>
36
63
  );
37
64
  }
@@ -0,0 +1,177 @@
1
+ import { useCallback, useId, useRef, useState, type ReactNode } from 'react';
2
+ import {
3
+ Badge,
4
+ Box,
5
+ Button,
6
+ Card,
7
+ Input,
8
+ InlineErrorList,
9
+ LoadingOverlay,
10
+ Stack,
11
+ Text,
12
+ Toast,
13
+ ToastList,
14
+ ToastSeverityIcon,
15
+ type InlineErrorEntry,
16
+ type ToastEntry,
17
+ type ToastSeverity,
18
+ } from '@xfilecom/front-core';
19
+
20
+ function SectionTitle({ children }: { children: ReactNode }) {
21
+ return (
22
+ <Text as="h2" variant="section" style={{ margin: 0 }}>
23
+ {children}
24
+ </Text>
25
+ );
26
+ }
27
+
28
+ export function FrontCoreShowcase() {
29
+ const uid = useId();
30
+ const toastSeq = useRef(0);
31
+ const errorSeq = useRef(0);
32
+ const [toasts, setToasts] = useState<ToastEntry[]>([]);
33
+ const [errors, setErrors] = useState<InlineErrorEntry[]>([]);
34
+ const [loading, setLoading] = useState(false);
35
+
36
+ const pushToast = useCallback(
37
+ (severity: ToastSeverity) => {
38
+ toastSeq.current += 1;
39
+ const id = `${uid}-t-${toastSeq.current}`;
40
+ setToasts((prev) => [...prev, { id, severity, message: `${severity} 메시지 예시` }]);
41
+ },
42
+ [uid],
43
+ );
44
+
45
+ const addError = useCallback(() => {
46
+ errorSeq.current += 1;
47
+ const id = `${uid}-e-${errorSeq.current}`;
48
+ setErrors((prev) => [...prev, { id, message: `샘플 에러 #${errorSeq.current}` }]);
49
+ }, [uid]);
50
+
51
+ return (
52
+ <>
53
+ <InlineErrorList errors={errors} onDismiss={(id) => setErrors((e) => e.filter((x) => x.id !== id))} />
54
+ <ToastList toasts={toasts} onDismiss={(id) => setToasts((t) => t.filter((x) => x.id !== id))} />
55
+ <LoadingOverlay active={loading} message="로딩 중…" />
56
+
57
+ <Stack direction="column" gap="lg" align="stretch">
58
+ <Card>
59
+ <Stack direction="column" gap="md" align="stretch">
60
+ <SectionTitle>Text</SectionTitle>
61
+ <Text variant="title">title</Text>
62
+ <Text variant="section">section</Text>
63
+ <Text variant="body">body — 본문 텍스트</Text>
64
+ <Text variant="muted">muted</Text>
65
+ <Text variant="small">small</Text>
66
+ <Text variant="accent">accent</Text>
67
+ </Stack>
68
+ </Card>
69
+
70
+ <Card>
71
+ <Stack direction="column" gap="md" align="stretch">
72
+ <SectionTitle>Badge</SectionTitle>
73
+ <Stack direction="row" gap="sm" align="center">
74
+ <Badge tone="neutral">neutral</Badge>
75
+ <Badge tone="accent">accent</Badge>
76
+ <Badge tone="success">success</Badge>
77
+ <Badge tone="danger">danger</Badge>
78
+ </Stack>
79
+ </Stack>
80
+ </Card>
81
+
82
+ <Card>
83
+ <Stack direction="column" gap="md" align="stretch">
84
+ <SectionTitle>Button</SectionTitle>
85
+ <Stack direction="row" gap="sm" align="center" style={{ flexWrap: 'wrap' }}>
86
+ <Button variant="primary">primary</Button>
87
+ <Button variant="secondary">secondary</Button>
88
+ <Button variant="outline">outline</Button>
89
+ <Button variant="muted">muted</Button>
90
+ <Button variant="ghost">ghost</Button>
91
+ <Button variant="primary" disabled>
92
+ disabled
93
+ </Button>
94
+ </Stack>
95
+ </Stack>
96
+ </Card>
97
+
98
+ <Card>
99
+ <Stack direction="column" gap="md" align="stretch">
100
+ <SectionTitle>Input</SectionTitle>
101
+ <label htmlFor={`${uid}-email`}>
102
+ <Text as="span" variant="labelBlock">
103
+ 이메일
104
+ </Text>
105
+ </label>
106
+ <Input id={`${uid}-email`} type="email" placeholder="you@example.com" autoComplete="email" />
107
+ <Input placeholder="비활성" disabled />
108
+ </Stack>
109
+ </Card>
110
+
111
+ <Card>
112
+ <Stack direction="column" gap="md" align="stretch">
113
+ <SectionTitle>Box · Stack · Card</SectionTitle>
114
+ <Stack direction="row" gap="md" align="stretch" style={{ flexWrap: 'wrap' }}>
115
+ <Box padding="md" style={{ border: '1px dashed var(--xfc-border-strong)', borderRadius: 'var(--xfc-radius-xs)' }}>
116
+ <Text variant="small">Box padding md</Text>
117
+ </Box>
118
+ <Card style={{ flex: '1 1 140px', minWidth: 0 }}>
119
+ <Text variant="small">중첩 Card</Text>
120
+ </Card>
121
+ </Stack>
122
+ </Stack>
123
+ </Card>
124
+
125
+ <Card>
126
+ <Stack direction="column" gap="md" align="stretch">
127
+ <SectionTitle>ToastSeverityIcon</SectionTitle>
128
+ <Stack direction="row" gap="lg" align="center" style={{ color: 'var(--xfc-fg)' }}>
129
+ <ToastSeverityIcon severity="info" />
130
+ <ToastSeverityIcon severity="success" />
131
+ <ToastSeverityIcon severity="warn" />
132
+ <ToastSeverityIcon severity="error" />
133
+ </Stack>
134
+ </Stack>
135
+ </Card>
136
+
137
+ <Card>
138
+ <Stack direction="column" gap="md" align="stretch">
139
+ <SectionTitle>Toast (단일)</SectionTitle>
140
+ <Toast severity="info" message="인라인 Toast 한 줄" onDismiss={() => {}} />
141
+ </Stack>
142
+ </Card>
143
+
144
+ <Card>
145
+ <Stack direction="column" gap="md" align="stretch">
146
+ <SectionTitle>ToastList · InlineErrorList · LoadingOverlay</SectionTitle>
147
+ <Text variant="small" style={{ color: 'var(--xfc-fg-muted)' }}>
148
+ ToastList / InlineErrorList / LoadingOverlay 는 화면 고정 레이어입니다. 아래 버튼으로 띄워 보세요.
149
+ </Text>
150
+ <Stack direction="row" gap="sm" align="center" style={{ flexWrap: 'wrap' }}>
151
+ <Button type="button" variant="secondary" onClick={() => pushToast('info')}>
152
+ toast info
153
+ </Button>
154
+ <Button type="button" variant="secondary" onClick={() => pushToast('success')}>
155
+ toast success
156
+ </Button>
157
+ <Button type="button" variant="secondary" onClick={() => pushToast('warn')}>
158
+ toast warn
159
+ </Button>
160
+ <Button type="button" variant="secondary" onClick={() => pushToast('error')}>
161
+ toast error
162
+ </Button>
163
+ </Stack>
164
+ <Stack direction="row" gap="sm" align="center" style={{ flexWrap: 'wrap' }}>
165
+ <Button type="button" variant="outline" onClick={addError}>
166
+ 에러 배너 추가
167
+ </Button>
168
+ <Button type="button" variant="outline" onClick={() => setLoading((v) => !v)}>
169
+ 로딩 오버레이 {loading ? '끄기' : '켜기'}
170
+ </Button>
171
+ </Stack>
172
+ </Stack>
173
+ </Card>
174
+ </Stack>
175
+ </>
176
+ );
177
+ }
@@ -1,7 +1,9 @@
1
1
  import React from 'react';
2
2
  import ReactDOM from 'react-dom/client';
3
3
  import '@xfilecom/front-core/tokens.css';
4
+ import '../../shared/src/styles/xfc-theme.css';
4
5
  import '@xfilecom/front-core/base.css';
6
+ import '../../shared/src/styles/app.css';
5
7
  import { App } from './App';
6
8
 
7
9
  ReactDOM.createRoot(document.getElementById('root')!).render(
@@ -10,7 +10,9 @@
10
10
  "types": "./src/index.ts",
11
11
  "import": "./src/index.ts",
12
12
  "default": "./src/index.ts"
13
- }
13
+ },
14
+ "./styles/xfc-theme.css": "./src/styles/xfc-theme.css",
15
+ "./styles/app.css": "./src/styles/app.css"
14
16
  },
15
17
  "scripts": {
16
18
  "build": "tsc -p tsconfig.build.json"
@@ -1,16 +1,17 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import { Card, Stack, Text } from '@xfilecom/front-core';
3
- import '../styles/app.css';
4
3
 
5
4
  export type ShellProps = {
6
5
  title: string;
7
6
  subtitle?: ReactNode;
8
7
  children?: ReactNode;
8
+ /** true면 폭 제한(480) 없음 — atoms 갤러리 등 */
9
+ wide?: boolean;
9
10
  };
10
11
 
11
- export function Shell({ title, subtitle, children }: ShellProps) {
12
+ export function Shell({ title, subtitle, children, wide = false }: ShellProps) {
12
13
  return (
13
- <div className="xfc-page xfc-page--narrow">
14
+ <div className={wide ? 'xfc-page' : 'xfc-page xfc-page--narrow'}>
14
15
  <header className="xfc-hero">
15
16
  <Text as="h1" variant="appbar">
16
17
  {title}
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * 앱 레이아웃 — `html/html` 모바일 프레임(폭 480)에 맞춤.
3
- * 토큰·atoms는 @xfilecom/front-core.
3
+ *
4
+ * 엔트리에서 @xfilecom/front-core/base.css 다음에 로드하세요.
5
+ * 이 파일은 tokens / xfc-theme / atoms 이후이므로
6
+ * - 레이아웃(.xfc-page 등)과
7
+ * - 필요 시 atoms(.xfc-btn, .xfc-toast 등) 덮어쓰기
8
+ * 를 같은 파일에 둘 수 있습니다.
4
9
  */
5
10
 
6
11
  .xfc-page {
@@ -61,3 +66,10 @@
61
66
  background: var(--xfc-bg);
62
67
  border-bottom: 1px solid var(--xfc-border-header);
63
68
  }
69
+
70
+ /* —— optional: front-core atoms 오버라이드 (base.css 이후 로드됨) —— */
71
+ /*
72
+ .xfc-btn--primary {
73
+ border-radius: var(--xfc-radius-md);
74
+ }
75
+ */
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 앱·브랜드별 디자인 토큰 덮어쓰기.
3
+ *
4
+ * 엔트리(main.tsx)에서 반드시 다음 순서로 넣으세요:
5
+ * 1. @xfilecom/front-core/tokens.css
6
+ * 2. 이 파일 (xfc-theme.css)
7
+ * 3. @xfilecom/front-core/base.css ← atoms 가 var(--xfc-*) 를 참조
8
+ * 4. app.css ← 레이아웃 + atoms(.xfc-*) 클래스 오버라이드
9
+ *
10
+ * 여기서 :root 의 --xfc-* 를 바꾸면 Button·Input·Toast 등 front-core atoms 스타일에 그대로 반영됩니다.
11
+ */
12
+
13
+ /* 예시 (필요할 때 주석 해제)
14
+ :root {
15
+ --xfc-accent: #2a5bff;
16
+ --xfc-accent-hover: #2248e6;
17
+ }
18
+ */