@ezpg/app-ui 0.3.0 → 0.5.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 (194) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/lib/module/blocks/brand-profile-card/brand-profile-card.js +2 -2
  3. package/lib/module/blocks/brand-profile-card/brand-profile-card.js.map +1 -1
  4. package/lib/module/blocks/bulk-ship-list/bulk-ship-list.js +1 -1
  5. package/lib/module/blocks/bulk-ship-list/bulk-ship-list.js.map +1 -1
  6. package/lib/module/blocks/claim-row/claim-row.js +6 -5
  7. package/lib/module/blocks/claim-row/claim-row.js.map +1 -1
  8. package/lib/module/blocks/color-swatch-picker/color-swatch-picker.js +1 -1
  9. package/lib/module/blocks/color-swatch-picker/color-swatch-picker.js.map +1 -1
  10. package/lib/module/blocks/copy-variant-picker/copy-variant-picker.js +3 -3
  11. package/lib/module/blocks/copy-variant-picker/copy-variant-picker.js.map +1 -1
  12. package/lib/module/blocks/credit-wallet/credit-wallet.js +1 -1
  13. package/lib/module/blocks/credit-wallet/credit-wallet.js.map +1 -1
  14. package/lib/module/blocks/image-grid/image-grid.js +9 -9
  15. package/lib/module/blocks/image-grid/image-grid.js.map +1 -1
  16. package/lib/module/blocks/internal/format.js +4 -0
  17. package/lib/module/blocks/internal/format.js.map +1 -1
  18. package/lib/module/blocks/invoice-row/invoice-row.js +2 -2
  19. package/lib/module/blocks/invoice-row/invoice-row.js.map +1 -1
  20. package/lib/module/blocks/logo-candidate-grid/logo-candidate-grid.js +1 -1
  21. package/lib/module/blocks/logo-candidate-grid/logo-candidate-grid.js.map +1 -1
  22. package/lib/module/blocks/oauth-buttons/oauth-buttons.js +2 -2
  23. package/lib/module/blocks/oauth-buttons/oauth-buttons.js.map +1 -1
  24. package/lib/module/blocks/option-editor/option-editor.js +3 -3
  25. package/lib/module/blocks/option-editor/option-editor.js.map +1 -1
  26. package/lib/module/blocks/order-card/index.js +1 -1
  27. package/lib/module/blocks/order-card/index.js.map +1 -1
  28. package/lib/module/blocks/order-card/order-card.js +31 -9
  29. package/lib/module/blocks/order-card/order-card.js.map +1 -1
  30. package/lib/module/blocks/plan-card/plan-card.js +2 -2
  31. package/lib/module/blocks/plan-card/plan-card.js.map +1 -1
  32. package/lib/module/blocks/product-list-empty/product-list-empty.js +5 -5
  33. package/lib/module/blocks/product-list-empty/product-list-empty.js.map +1 -1
  34. package/lib/module/blocks/review-row/review-row.js +17 -17
  35. package/lib/module/blocks/review-row/review-row.js.map +1 -1
  36. package/lib/module/blocks/sales-chart/sales-chart.js +2 -2
  37. package/lib/module/blocks/sales-chart/sales-chart.js.map +1 -1
  38. package/lib/module/blocks/selection-bar/selection-bar.js +1 -1
  39. package/lib/module/blocks/selection-bar/selection-bar.js.map +1 -1
  40. package/lib/module/blocks/shipping-tracking-row/shipping-tracking-row.js +1 -1
  41. package/lib/module/blocks/shipping-tracking-row/shipping-tracking-row.js.map +1 -1
  42. package/lib/module/blocks/site-preview-frame/site-preview-frame.js +1 -1
  43. package/lib/module/blocks/site-preview-frame/site-preview-frame.js.map +1 -1
  44. package/lib/module/blocks/step-screen/step-screen.js +18 -14
  45. package/lib/module/blocks/step-screen/step-screen.js.map +1 -1
  46. package/lib/module/blocks/supply-item-card/supply-item-card.js +5 -5
  47. package/lib/module/blocks/supply-item-card/supply-item-card.js.map +1 -1
  48. package/lib/module/components/button/button.js +12 -17
  49. package/lib/module/components/button/button.js.map +1 -1
  50. package/lib/module/components/chip/chip.js +2 -2
  51. package/lib/module/components/chip/chip.js.map +1 -1
  52. package/lib/module/components/dialog/dialog.js +5 -5
  53. package/lib/module/components/dialog/dialog.js.map +1 -1
  54. package/lib/module/components/empty-state/empty-state.js +4 -4
  55. package/lib/module/components/empty-state/empty-state.js.map +1 -1
  56. package/lib/module/components/field/field.js +15 -14
  57. package/lib/module/components/field/field.js.map +1 -1
  58. package/lib/module/components/list-row/index.js.map +1 -1
  59. package/lib/module/components/list-row/list-row.js +38 -10
  60. package/lib/module/components/list-row/list-row.js.map +1 -1
  61. package/lib/module/components/segmented-tabs/segmented-tabs.js +2 -2
  62. package/lib/module/components/segmented-tabs/segmented-tabs.js.map +1 -1
  63. package/lib/module/components/status-badge/status-badge.js +1 -1
  64. package/lib/module/components/status-badge/status-badge.js.map +1 -1
  65. package/lib/module/components/text/text.js +5 -0
  66. package/lib/module/components/text/text.js.map +1 -1
  67. package/lib/module/components/toast/toast.js +4 -4
  68. package/lib/module/components/toast/toast.js.map +1 -1
  69. package/lib/module/index.js +1 -0
  70. package/lib/module/index.js.map +1 -1
  71. package/lib/module/templates/editor-template.js +142 -0
  72. package/lib/module/templates/editor-template.js.map +1 -0
  73. package/lib/module/templates/group.js +45 -0
  74. package/lib/module/templates/group.js.map +1 -0
  75. package/lib/module/templates/index.js +10 -0
  76. package/lib/module/templates/index.js.map +1 -0
  77. package/lib/module/templates/list-template.js +98 -0
  78. package/lib/module/templates/list-template.js.map +1 -0
  79. package/lib/module/templates/parts.js +115 -0
  80. package/lib/module/templates/parts.js.map +1 -0
  81. package/lib/module/templates/result-template.js +233 -0
  82. package/lib/module/templates/result-template.js.map +1 -0
  83. package/lib/module/templates/scroll-templates.js +100 -0
  84. package/lib/module/templates/scroll-templates.js.map +1 -0
  85. package/lib/module/templates/system-template.js +64 -0
  86. package/lib/module/templates/system-template.js.map +1 -0
  87. package/lib/module/theme/base.js +35 -2
  88. package/lib/module/theme/base.js.map +1 -1
  89. package/lib/module/theme/contract.js +6 -0
  90. package/lib/module/theme/contract.js.map +1 -0
  91. package/lib/module/theme/tokens.js +10 -2
  92. package/lib/module/theme/tokens.js.map +1 -1
  93. package/lib/typescript/src/blocks/claim-row/claim-row.d.ts.map +1 -1
  94. package/lib/typescript/src/blocks/color-swatch-picker/color-swatch-picker.d.ts.map +1 -1
  95. package/lib/typescript/src/blocks/image-grid/image-grid.d.ts.map +1 -1
  96. package/lib/typescript/src/blocks/internal/format.d.ts +1 -0
  97. package/lib/typescript/src/blocks/internal/format.d.ts.map +1 -1
  98. package/lib/typescript/src/blocks/invoice-row/invoice-row.d.ts.map +1 -1
  99. package/lib/typescript/src/blocks/oauth-buttons/oauth-buttons.d.ts.map +1 -1
  100. package/lib/typescript/src/blocks/order-card/index.d.ts +1 -1
  101. package/lib/typescript/src/blocks/order-card/index.d.ts.map +1 -1
  102. package/lib/typescript/src/blocks/order-card/order-card.d.ts +10 -0
  103. package/lib/typescript/src/blocks/order-card/order-card.d.ts.map +1 -1
  104. package/lib/typescript/src/blocks/product-list-empty/product-list-empty.d.ts.map +1 -1
  105. package/lib/typescript/src/blocks/review-row/review-row.d.ts.map +1 -1
  106. package/lib/typescript/src/blocks/step-screen/step-screen.d.ts +3 -1
  107. package/lib/typescript/src/blocks/step-screen/step-screen.d.ts.map +1 -1
  108. package/lib/typescript/src/components/button/button.d.ts +2 -2
  109. package/lib/typescript/src/components/button/button.d.ts.map +1 -1
  110. package/lib/typescript/src/components/empty-state/empty-state.d.ts.map +1 -1
  111. package/lib/typescript/src/components/field/field.d.ts.map +1 -1
  112. package/lib/typescript/src/components/list-row/index.d.ts +1 -1
  113. package/lib/typescript/src/components/list-row/index.d.ts.map +1 -1
  114. package/lib/typescript/src/components/list-row/list-row.d.ts +4 -1
  115. package/lib/typescript/src/components/list-row/list-row.d.ts.map +1 -1
  116. package/lib/typescript/src/components/status-badge/status-badge.d.ts +2 -1
  117. package/lib/typescript/src/components/status-badge/status-badge.d.ts.map +1 -1
  118. package/lib/typescript/src/components/text/text.d.ts +1 -1
  119. package/lib/typescript/src/components/text/text.d.ts.map +1 -1
  120. package/lib/typescript/src/components/toast/toast.d.ts +2 -1
  121. package/lib/typescript/src/components/toast/toast.d.ts.map +1 -1
  122. package/lib/typescript/src/index.d.ts +1 -0
  123. package/lib/typescript/src/index.d.ts.map +1 -1
  124. package/lib/typescript/src/templates/editor-template.d.ts +22 -0
  125. package/lib/typescript/src/templates/editor-template.d.ts.map +1 -0
  126. package/lib/typescript/src/templates/group.d.ts +8 -0
  127. package/lib/typescript/src/templates/group.d.ts.map +1 -0
  128. package/lib/typescript/src/templates/index.d.ts +15 -0
  129. package/lib/typescript/src/templates/index.d.ts.map +1 -0
  130. package/lib/typescript/src/templates/list-template.d.ts +27 -0
  131. package/lib/typescript/src/templates/list-template.d.ts.map +1 -0
  132. package/lib/typescript/src/templates/parts.d.ts +27 -0
  133. package/lib/typescript/src/templates/parts.d.ts.map +1 -0
  134. package/lib/typescript/src/templates/result-template.d.ts +21 -0
  135. package/lib/typescript/src/templates/result-template.d.ts.map +1 -0
  136. package/lib/typescript/src/templates/scroll-templates.d.ts +32 -0
  137. package/lib/typescript/src/templates/scroll-templates.d.ts.map +1 -0
  138. package/lib/typescript/src/templates/system-template.d.ts +12 -0
  139. package/lib/typescript/src/templates/system-template.d.ts.map +1 -0
  140. package/lib/typescript/src/theme/base.d.ts +35 -2
  141. package/lib/typescript/src/theme/base.d.ts.map +1 -1
  142. package/lib/typescript/src/theme/contract.d.ts +7 -0
  143. package/lib/typescript/src/theme/contract.d.ts.map +1 -0
  144. package/lib/typescript/src/theme/tokens.d.ts.map +1 -1
  145. package/lib/typescript/src/types/index.d.ts +23 -1
  146. package/lib/typescript/src/types/index.d.ts.map +1 -1
  147. package/package.json +1 -1
  148. package/src/blocks/brand-profile-card/brand-profile-card.tsx +2 -2
  149. package/src/blocks/bulk-ship-list/bulk-ship-list.tsx +1 -1
  150. package/src/blocks/claim-row/claim-row.tsx +12 -3
  151. package/src/blocks/color-swatch-picker/color-swatch-picker.tsx +5 -1
  152. package/src/blocks/copy-variant-picker/copy-variant-picker.tsx +3 -3
  153. package/src/blocks/credit-wallet/credit-wallet.tsx +1 -1
  154. package/src/blocks/image-grid/image-grid.tsx +15 -8
  155. package/src/blocks/internal/format.ts +5 -0
  156. package/src/blocks/invoice-row/invoice-row.tsx +5 -1
  157. package/src/blocks/logo-candidate-grid/logo-candidate-grid.tsx +1 -1
  158. package/src/blocks/oauth-buttons/oauth-buttons.tsx +6 -2
  159. package/src/blocks/option-editor/option-editor.tsx +3 -3
  160. package/src/blocks/order-card/index.ts +4 -0
  161. package/src/blocks/order-card/order-card.tsx +50 -6
  162. package/src/blocks/plan-card/plan-card.tsx +2 -2
  163. package/src/blocks/product-list-empty/product-list-empty.tsx +11 -4
  164. package/src/blocks/review-row/review-row.tsx +26 -15
  165. package/src/blocks/sales-chart/sales-chart.tsx +2 -2
  166. package/src/blocks/selection-bar/selection-bar.tsx +1 -1
  167. package/src/blocks/shipping-tracking-row/shipping-tracking-row.tsx +1 -1
  168. package/src/blocks/site-preview-frame/site-preview-frame.tsx +1 -1
  169. package/src/blocks/step-screen/step-screen.tsx +24 -12
  170. package/src/blocks/supply-item-card/supply-item-card.tsx +5 -5
  171. package/src/components/button/button.tsx +22 -14
  172. package/src/components/chip/chip.tsx +2 -2
  173. package/src/components/dialog/dialog.tsx +5 -5
  174. package/src/components/empty-state/empty-state.tsx +8 -4
  175. package/src/components/field/field.tsx +15 -14
  176. package/src/components/list-row/index.ts +1 -1
  177. package/src/components/list-row/list-row.tsx +35 -8
  178. package/src/components/segmented-tabs/segmented-tabs.tsx +2 -2
  179. package/src/components/status-badge/status-badge.tsx +3 -3
  180. package/src/components/text/text.tsx +4 -1
  181. package/src/components/toast/toast.tsx +6 -5
  182. package/src/index.tsx +1 -0
  183. package/src/templates/editor-template.tsx +164 -0
  184. package/src/templates/group.tsx +49 -0
  185. package/src/templates/index.ts +28 -0
  186. package/src/templates/list-template.tsx +124 -0
  187. package/src/templates/parts.tsx +138 -0
  188. package/src/templates/result-template.tsx +268 -0
  189. package/src/templates/scroll-templates.tsx +140 -0
  190. package/src/templates/system-template.tsx +70 -0
  191. package/src/theme/base.ts +38 -2
  192. package/src/theme/contract.ts +21 -0
  193. package/src/theme/tokens.ts +11 -0
  194. package/src/types/index.ts +15 -1
@@ -0,0 +1,268 @@
1
+ import { useState, type ReactNode } from 'react';
2
+ import {
3
+ Modal,
4
+ ScrollView,
5
+ View,
6
+ type LayoutChangeEvent,
7
+ type NativeScrollEvent,
8
+ type NativeSyntheticEvent,
9
+ } from 'react-native';
10
+ import { Text } from '../components/text/text';
11
+ import { CloseGlyph } from '../internal/glyphs';
12
+ import { PressableBase } from '../internal/pressable-base';
13
+ import { useTheme } from '../theme/theme-provider';
14
+ import { makeStyles } from '../utils/styles';
15
+ import {
16
+ BottomBar,
17
+ Notice,
18
+ type TemplateAction,
19
+ type TemplateNotice,
20
+ } from './parts';
21
+
22
+ export type ResultPreview = {
23
+ key: string;
24
+ label: string;
25
+ render: (mode: 'card' | 'full') => ReactNode;
26
+ };
27
+
28
+ export type ResultTemplateProps = {
29
+ previews: readonly ResultPreview[];
30
+ index: number;
31
+ onIndexChange: (index: number) => void;
32
+ progress?: ReactNode;
33
+ notice?: TemplateNotice;
34
+ primaryAction?: TemplateAction;
35
+ secondaryAction?: TemplateAction;
36
+ openLabel?: string;
37
+ closeLabel?: string;
38
+ bottomInset?: number;
39
+ };
40
+
41
+ const useStyles = makeStyles((t) => ({
42
+ root: { flex: 1, backgroundColor: t.colors.background },
43
+ body: { flex: 1, paddingTop: t.space[2] },
44
+ pager: { flex: 1 },
45
+ page: { gap: t.space[2] },
46
+ card: {
47
+ flex: 1,
48
+ overflow: 'hidden',
49
+ borderRadius: t.radius.lg,
50
+ borderWidth: 1,
51
+ borderColor: t.colors.border,
52
+ backgroundColor: t.colors.muted,
53
+ },
54
+ caption: {
55
+ flexDirection: 'row',
56
+ justifyContent: 'space-between',
57
+ alignItems: 'center',
58
+ },
59
+ dots: {
60
+ flexDirection: 'row',
61
+ justifyContent: 'center',
62
+ gap: t.space[2],
63
+ paddingVertical: t.layout.groupGap,
64
+ },
65
+ dot: {
66
+ width: t.space[2],
67
+ height: t.space[2],
68
+ borderRadius: t.radius.full,
69
+ backgroundColor: t.colors.input,
70
+ },
71
+ dotOn: { width: t.space[5], backgroundColor: t.colors.foreground },
72
+ progress: { flex: 1, paddingHorizontal: t.layout.screenX },
73
+ notice: { paddingBottom: t.layout.groupGap },
74
+ viewer: { flex: 1, backgroundColor: t.colors.background },
75
+ viewerHead: {
76
+ height: t.headerHeight,
77
+ flexDirection: 'row',
78
+ alignItems: 'center',
79
+ paddingHorizontal: t.space[1],
80
+ gap: t.space[1],
81
+ },
82
+ close: {
83
+ width: t.minTouchSize,
84
+ height: t.minTouchSize,
85
+ alignItems: 'center',
86
+ justifyContent: 'center',
87
+ },
88
+ full: { flex: 1 },
89
+ }));
90
+
91
+ function Viewer({
92
+ previews,
93
+ index,
94
+ onIndexChange,
95
+ onClose,
96
+ primaryAction,
97
+ closeLabel,
98
+ bottomInset,
99
+ }: {
100
+ previews: readonly ResultPreview[];
101
+ index: number;
102
+ onIndexChange: (index: number) => void;
103
+ onClose: () => void;
104
+ primaryAction?: TemplateAction;
105
+ closeLabel: string;
106
+ bottomInset: number;
107
+ }) {
108
+ const t = useTheme();
109
+ const s = useStyles();
110
+ const [width, setWidth] = useState(0);
111
+ const current = previews[index];
112
+ return (
113
+ <View style={s.viewer}>
114
+ <View style={s.viewerHead}>
115
+ <PressableBase
116
+ onPress={onClose}
117
+ accessibilityLabel={closeLabel}
118
+ style={s.close}
119
+ >
120
+ <CloseGlyph color={t.colors.foreground} size={t.iconSize.md} />
121
+ </PressableBase>
122
+ <Text variant="bodyLg" weight="semibold" accessibilityRole="header">
123
+ {current
124
+ ? `${current.label} · ${index + 1} / ${previews.length}`
125
+ : ''}
126
+ </Text>
127
+ </View>
128
+ <ScrollView
129
+ horizontal
130
+ pagingEnabled
131
+ showsHorizontalScrollIndicator={false}
132
+ onLayout={(e: LayoutChangeEvent) =>
133
+ setWidth(e.nativeEvent.layout.width)
134
+ }
135
+ contentOffset={{ x: index * width, y: 0 }}
136
+ onMomentumScrollEnd={(e: NativeSyntheticEvent<NativeScrollEvent>) =>
137
+ width &&
138
+ onIndexChange(Math.round(e.nativeEvent.contentOffset.x / width))
139
+ }
140
+ style={s.full}
141
+ >
142
+ {previews.map((preview) => (
143
+ <View key={preview.key} style={{ width }}>
144
+ {preview.render('full')}
145
+ </View>
146
+ ))}
147
+ </ScrollView>
148
+ {primaryAction ? (
149
+ <BottomBar primaryAction={primaryAction} bottomInset={bottomInset} />
150
+ ) : null}
151
+ </View>
152
+ );
153
+ }
154
+
155
+ export function ResultTemplate({
156
+ previews,
157
+ index,
158
+ onIndexChange,
159
+ progress,
160
+ notice,
161
+ primaryAction,
162
+ secondaryAction,
163
+ openLabel = '크게 보기',
164
+ closeLabel = '닫기',
165
+ bottomInset = 0,
166
+ }: ResultTemplateProps) {
167
+ const t = useTheme();
168
+ const s = useStyles();
169
+ const [width, setWidth] = useState(0);
170
+ const [open, setOpen] = useState(false);
171
+ const page = Math.max(0, width - t.layout.screenX * 2 - t.space[6]);
172
+ const step = page + t.layout.itemGap;
173
+
174
+ return (
175
+ <View style={s.root}>
176
+ <View
177
+ style={s.body}
178
+ onLayout={(e: LayoutChangeEvent) =>
179
+ setWidth(e.nativeEvent.layout.width)
180
+ }
181
+ >
182
+ {progress ? (
183
+ <View style={s.progress}>{progress}</View>
184
+ ) : (
185
+ <>
186
+ <ScrollView
187
+ horizontal
188
+ showsHorizontalScrollIndicator={false}
189
+ snapToInterval={step || undefined}
190
+ decelerationRate="fast"
191
+ contentContainerStyle={{
192
+ gap: t.layout.itemGap,
193
+ paddingHorizontal: t.layout.screenX,
194
+ }}
195
+ onMomentumScrollEnd={(
196
+ e: NativeSyntheticEvent<NativeScrollEvent>
197
+ ) =>
198
+ step &&
199
+ onIndexChange(Math.round(e.nativeEvent.contentOffset.x / step))
200
+ }
201
+ style={s.pager}
202
+ >
203
+ {previews.map((preview, i) => (
204
+ <View key={preview.key} style={[s.page, { width: page }]}>
205
+ <PressableBase
206
+ onPress={() => {
207
+ onIndexChange(i);
208
+ setOpen(true);
209
+ }}
210
+ accessibilityLabel={`${preview.label} ${openLabel}`}
211
+ style={s.card}
212
+ >
213
+ {preview.render('card')}
214
+ </PressableBase>
215
+ <View style={s.caption}>
216
+ <Text variant="label" weight="semibold">
217
+ {preview.label}
218
+ </Text>
219
+ <Text variant="label" tone="muted">
220
+ {openLabel}
221
+ </Text>
222
+ </View>
223
+ </View>
224
+ ))}
225
+ </ScrollView>
226
+ {previews.length > 1 ? (
227
+ <View style={s.dots}>
228
+ {previews.map((preview, i) => (
229
+ <View
230
+ key={preview.key}
231
+ style={[s.dot, i === index ? s.dotOn : null]}
232
+ />
233
+ ))}
234
+ </View>
235
+ ) : null}
236
+ </>
237
+ )}
238
+ </View>
239
+ {notice ? (
240
+ <View style={s.notice}>
241
+ <Notice notice={notice} />
242
+ </View>
243
+ ) : null}
244
+ {primaryAction ? (
245
+ <BottomBar
246
+ primaryAction={primaryAction}
247
+ secondaryAction={secondaryAction}
248
+ bottomInset={bottomInset}
249
+ />
250
+ ) : null}
251
+ <Modal
252
+ visible={open}
253
+ animationType="slide"
254
+ onRequestClose={() => setOpen(false)}
255
+ >
256
+ <Viewer
257
+ previews={previews}
258
+ index={index}
259
+ onIndexChange={onIndexChange}
260
+ onClose={() => setOpen(false)}
261
+ primaryAction={primaryAction}
262
+ closeLabel={closeLabel}
263
+ bottomInset={bottomInset}
264
+ />
265
+ </Modal>
266
+ </View>
267
+ );
268
+ }
@@ -0,0 +1,140 @@
1
+ import type { ReactNode } from 'react';
2
+ import {
3
+ KeyboardAvoidingView,
4
+ RefreshControl,
5
+ ScrollView,
6
+ View,
7
+ } from 'react-native';
8
+ import { useTheme } from '../theme/theme-provider';
9
+ import { makeStyles } from '../utils/styles';
10
+ import {
11
+ BottomBar,
12
+ Notice,
13
+ type TemplateAction,
14
+ type TemplateNotice,
15
+ } from './parts';
16
+
17
+ type ScrollTemplateProps = {
18
+ top?: ReactNode;
19
+ notice?: TemplateNotice;
20
+ primaryAction?: TemplateAction;
21
+ secondaryAction?: TemplateAction;
22
+ refreshing?: boolean;
23
+ onRefresh?: () => void;
24
+ bottomInset?: number;
25
+ children: ReactNode;
26
+ };
27
+
28
+ const useStyles = makeStyles((t) => ({
29
+ root: { flex: 1, backgroundColor: t.colors.background },
30
+ content: { paddingBottom: t.layout.sectionGap },
31
+ top: {
32
+ flexDirection: 'row',
33
+ flexWrap: 'wrap',
34
+ gap: t.layout.itemGap,
35
+ paddingTop: t.space[2],
36
+ paddingHorizontal: t.layout.screenX,
37
+ },
38
+ notice: { paddingTop: t.layout.groupGap },
39
+ }));
40
+
41
+ function ScrollTemplate({
42
+ top,
43
+ notice,
44
+ primaryAction,
45
+ secondaryAction,
46
+ refreshing = false,
47
+ onRefresh,
48
+ bottomInset = 0,
49
+ children,
50
+ }: ScrollTemplateProps) {
51
+ const t = useTheme();
52
+ const s = useStyles();
53
+ return (
54
+ <KeyboardAvoidingView behavior="padding" style={s.root}>
55
+ <ScrollView
56
+ contentContainerStyle={[
57
+ s.content,
58
+ primaryAction
59
+ ? null
60
+ : { paddingBottom: t.layout.sectionGap + bottomInset },
61
+ ]}
62
+ keyboardShouldPersistTaps="handled"
63
+ refreshControl={
64
+ onRefresh ? (
65
+ <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
66
+ ) : undefined
67
+ }
68
+ >
69
+ {top ? <View style={s.top}>{top}</View> : null}
70
+ {notice ? (
71
+ <View style={s.notice}>
72
+ <Notice notice={notice} />
73
+ </View>
74
+ ) : null}
75
+ {children}
76
+ </ScrollView>
77
+ {primaryAction ? (
78
+ <BottomBar
79
+ primaryAction={primaryAction}
80
+ secondaryAction={secondaryAction}
81
+ bottomInset={bottomInset}
82
+ />
83
+ ) : null}
84
+ </KeyboardAvoidingView>
85
+ );
86
+ }
87
+
88
+ export type DetailTemplateProps = {
89
+ status?: ReactNode;
90
+ notice?: TemplateNotice;
91
+ primaryAction?: TemplateAction;
92
+ secondaryAction?: TemplateAction;
93
+ bottomInset?: number;
94
+ children: ReactNode;
95
+ };
96
+
97
+ export function DetailTemplate({ status, ...rest }: DetailTemplateProps) {
98
+ return <ScrollTemplate top={status} {...rest} />;
99
+ }
100
+
101
+ export type FormTemplateProps = {
102
+ notice?: TemplateNotice;
103
+ saveAction: TemplateAction;
104
+ bottomInset?: number;
105
+ children: ReactNode;
106
+ };
107
+
108
+ export function FormTemplate({ saveAction, ...rest }: FormTemplateProps) {
109
+ return <ScrollTemplate primaryAction={saveAction} {...rest} />;
110
+ }
111
+
112
+ export type MenuTemplateProps = {
113
+ bottomInset?: number;
114
+ children: ReactNode;
115
+ };
116
+
117
+ export function MenuTemplate(props: MenuTemplateProps) {
118
+ return <ScrollTemplate {...props} />;
119
+ }
120
+
121
+ export type DashboardTemplateProps = {
122
+ banner?: ReactNode;
123
+ refreshing?: boolean;
124
+ onRefresh?: () => void;
125
+ bottomInset?: number;
126
+ children: ReactNode;
127
+ };
128
+
129
+ export function DashboardTemplate({
130
+ banner,
131
+ children,
132
+ ...rest
133
+ }: DashboardTemplateProps) {
134
+ return (
135
+ <ScrollTemplate {...rest}>
136
+ {banner}
137
+ {children}
138
+ </ScrollTemplate>
139
+ );
140
+ }
@@ -0,0 +1,70 @@
1
+ import type { ReactNode } from 'react';
2
+ import { View } from 'react-native';
3
+ import { Text } from '../components/text/text';
4
+ import { renderIcon, type IconSlot } from '../internal/icon';
5
+ import { useTheme } from '../theme/theme-provider';
6
+ import { makeStyles } from '../utils/styles';
7
+ import { BottomBar, type TemplateAction } from './parts';
8
+
9
+ export type SystemTemplateProps = {
10
+ icon?: IconSlot;
11
+ title: string;
12
+ primaryAction?: TemplateAction;
13
+ actions?: ReactNode;
14
+ bottomInset?: number;
15
+ };
16
+
17
+ const useStyles = makeStyles((t) => ({
18
+ root: { flex: 1, backgroundColor: t.colors.background },
19
+ center: {
20
+ flex: 1,
21
+ alignItems: 'center',
22
+ justifyContent: 'center',
23
+ gap: t.layout.titleGap,
24
+ paddingHorizontal: t.layout.screenX,
25
+ },
26
+ title: { textAlign: 'center' },
27
+ actions: {
28
+ gap: t.layout.itemGap,
29
+ paddingHorizontal: t.layout.screenX,
30
+ paddingTop: t.layout.footerTop,
31
+ },
32
+ }));
33
+
34
+ export function SystemTemplate({
35
+ icon,
36
+ title,
37
+ primaryAction,
38
+ actions,
39
+ bottomInset = 0,
40
+ }: SystemTemplateProps) {
41
+ const t = useTheme();
42
+ const s = useStyles();
43
+ return (
44
+ <View style={s.root}>
45
+ <View style={s.center}>
46
+ {icon
47
+ ? renderIcon(icon, {
48
+ color: t.colors.foreground,
49
+ size: t.controlSize.lg,
50
+ })
51
+ : null}
52
+ <Text variant="screenTitle" accessibilityRole="header" style={s.title}>
53
+ {title}
54
+ </Text>
55
+ </View>
56
+ {actions ? (
57
+ <View
58
+ style={[
59
+ s.actions,
60
+ { paddingBottom: t.layout.footerBottom + bottomInset },
61
+ ]}
62
+ >
63
+ {actions}
64
+ </View>
65
+ ) : primaryAction ? (
66
+ <BottomBar primaryAction={primaryAction} bottomInset={bottomInset} />
67
+ ) : null}
68
+ </View>
69
+ );
70
+ }
package/src/theme/base.ts CHANGED
@@ -3,18 +3,19 @@ export const semantic = {
3
3
  background: '#ffffff',
4
4
  foreground: '#0a0a0a',
5
5
  muted: '#f5f5f5',
6
- mutedForeground: '#737373',
6
+ mutedForeground: '#525252',
7
7
  border: '#e5e5e5',
8
8
  input: '#d4d4d4',
9
9
  ring: '#a3a3a3',
10
10
  primary: '#171717',
11
11
  primaryForeground: '#fafafa',
12
- danger: '#dc2626',
12
+ danger: '#b91c1c',
13
13
  dangerForeground: '#ffffff',
14
14
  success: '#15803d',
15
15
  successForeground: '#ffffff',
16
16
  warning: '#f59e0b',
17
17
  warningForeground: '#0a0a0a',
18
+ warningText: '#b45309',
18
19
  info: '#2563eb',
19
20
  infoForeground: '#ffffff',
20
21
  },
@@ -34,6 +35,7 @@ export const semantic = {
34
35
  successForeground: '#0a0a0a',
35
36
  warning: '#fbbf24',
36
37
  warningForeground: '#0a0a0a',
38
+ warningText: '#fbbf24',
37
39
  info: '#60a5fa',
38
40
  infoForeground: '#0a0a0a',
39
41
  },
@@ -66,6 +68,11 @@ export const typography = {
66
68
  lineHeight: 16,
67
69
  fontWeight: '400',
68
70
  },
71
+ label: {
72
+ fontSize: 14,
73
+ lineHeight: 20,
74
+ fontWeight: '400',
75
+ },
69
76
  body: {
70
77
  fontSize: 15,
71
78
  lineHeight: 22,
@@ -81,6 +88,11 @@ export const typography = {
81
88
  lineHeight: 28,
82
89
  fontWeight: '600',
83
90
  },
91
+ screenTitle: {
92
+ fontSize: 24,
93
+ lineHeight: 32,
94
+ fontWeight: '700',
95
+ },
84
96
  display: {
85
97
  fontSize: 28,
86
98
  lineHeight: 34,
@@ -111,3 +123,27 @@ export const touch = {
111
123
  ios: 44,
112
124
  android: 48,
113
125
  } as const;
126
+
127
+ export const layout = {
128
+ screenX: 16,
129
+ screenTop: 24,
130
+ titleGap: 24,
131
+ sectionGap: 32,
132
+ groupGap: 12,
133
+ itemGap: 8,
134
+ footerTop: 12,
135
+ footerBottom: 16,
136
+ } as const;
137
+
138
+ export const controlSize = {
139
+ sm: 44,
140
+ md: 48,
141
+ lg: 52,
142
+ } as const;
143
+
144
+ export const frame = {
145
+ header: 52,
146
+ listRow: 56,
147
+ listRow2: 72,
148
+ listRow3: 88,
149
+ } as const;
@@ -0,0 +1,21 @@
1
+ export const TONES = [
2
+ 'neutral',
3
+ 'info',
4
+ 'success',
5
+ 'warning',
6
+ 'danger',
7
+ 'primary',
8
+ ] as const;
9
+ export type Tone = (typeof TONES)[number];
10
+
11
+ export const BUTTON_VARIANTS = [
12
+ 'primary',
13
+ 'secondary',
14
+ 'outline',
15
+ 'ghost',
16
+ 'danger',
17
+ ] as const;
18
+ export type ButtonVariant = (typeof BUTTON_VARIANTS)[number];
19
+
20
+ export const BUTTON_SIZES = ['sm', 'md', 'lg'] as const;
21
+ export type ButtonSize = (typeof BUTTON_SIZES)[number];
@@ -1,7 +1,10 @@
1
1
  import { Platform } from 'react-native';
2
2
  import type { ColorScheme, ThemeColors, ThemeTokens } from '../types';
3
3
  import {
4
+ controlSize,
5
+ frame,
4
6
  iconSize,
7
+ layout,
5
8
  motion,
6
9
  opacity,
7
10
  radius,
@@ -25,4 +28,12 @@ export const tokens: ThemeTokens = {
25
28
  opacity,
26
29
  iconSize,
27
30
  minTouchSize: Platform.OS === 'ios' ? touch.ios : touch.android,
31
+ layout,
32
+ controlSize,
33
+ headerHeight: frame.header,
34
+ listRow: {
35
+ oneLine: frame.listRow,
36
+ twoLine: frame.listRow2,
37
+ threeLine: frame.listRow3,
38
+ },
28
39
  };
@@ -18,12 +18,13 @@ export type ThemeColors = {
18
18
  successForeground: string;
19
19
  warning: string;
20
20
  warningForeground: string;
21
+ warningText: string;
21
22
  info: string;
22
23
  infoForeground: string;
23
24
  };
24
25
 
25
26
  export type TypographyVariant =
26
- 'caption' | 'body' | 'bodyLg' | 'title' | 'display';
27
+ 'caption' | 'label' | 'body' | 'bodyLg' | 'title' | 'screenTitle' | 'display';
27
28
 
28
29
  export type TypographyToken = {
29
30
  fontSize: number;
@@ -54,6 +55,19 @@ export type ThemeTokens = {
54
55
  opacity: { pressed: number; disabled: number };
55
56
  iconSize: { sm: number; md: number };
56
57
  minTouchSize: number;
58
+ layout: {
59
+ screenX: number;
60
+ screenTop: number;
61
+ titleGap: number;
62
+ sectionGap: number;
63
+ groupGap: number;
64
+ itemGap: number;
65
+ footerTop: number;
66
+ footerBottom: number;
67
+ };
68
+ controlSize: { sm: number; md: number; lg: number };
69
+ headerHeight: number;
70
+ listRow: { oneLine: number; twoLine: number; threeLine: number };
57
71
  };
58
72
 
59
73
  export type Theme = ThemeTokens & {