@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,164 @@
1
+ import type { ReactNode } from 'react';
2
+ import { ScrollView, View } from 'react-native';
3
+ import { Button } from '../components/button/button';
4
+ import { Text } from '../components/text/text';
5
+ import { CloseGlyph } from '../internal/glyphs';
6
+ import { PressableBase } from '../internal/pressable-base';
7
+ import { useTheme } from '../theme/theme-provider';
8
+ import { makeStyles } from '../utils/styles';
9
+
10
+ export type EditorTool = {
11
+ key: string;
12
+ label: string;
13
+ onPress: () => void;
14
+ disabled?: boolean;
15
+ };
16
+
17
+ export type EditorSheet = {
18
+ title: string;
19
+ onClose: () => void;
20
+ headerAction?: ReactNode;
21
+ children: ReactNode;
22
+ };
23
+
24
+ export type EditorTemplateProps = {
25
+ preview: ReactNode;
26
+ tools: readonly EditorTool[];
27
+ sheet?: EditorSheet | null;
28
+ closeLabel?: string;
29
+ bottomInset?: number;
30
+ };
31
+
32
+ const useStyles = makeStyles((t) => ({
33
+ root: { flex: 1, backgroundColor: t.colors.muted },
34
+ canvas: {
35
+ flex: 1,
36
+ paddingTop: t.layout.groupGap,
37
+ paddingHorizontal: t.layout.screenX,
38
+ },
39
+ preview: {
40
+ flex: 1,
41
+ overflow: 'hidden',
42
+ borderTopLeftRadius: t.radius.lg,
43
+ borderTopRightRadius: t.radius.lg,
44
+ backgroundColor: t.colors.background,
45
+ },
46
+ tools: {
47
+ flexDirection: 'row',
48
+ gap: t.layout.itemGap,
49
+ paddingHorizontal: t.layout.screenX,
50
+ paddingTop: t.layout.footerTop,
51
+ borderTopWidth: 1,
52
+ borderTopColor: t.colors.border,
53
+ backgroundColor: t.colors.background,
54
+ },
55
+ tool: { flex: 1 },
56
+ sheet: {
57
+ position: 'absolute',
58
+ left: 0,
59
+ right: 0,
60
+ bottom: 0,
61
+ maxHeight: '60%',
62
+ borderTopLeftRadius: t.radius.lg,
63
+ borderTopRightRadius: t.radius.lg,
64
+ borderTopWidth: 1,
65
+ borderColor: t.colors.border,
66
+ backgroundColor: t.colors.background,
67
+ },
68
+ grab: {
69
+ alignSelf: 'center',
70
+ width: t.space[8],
71
+ height: t.space[1],
72
+ marginTop: t.space[2],
73
+ borderRadius: t.radius.full,
74
+ backgroundColor: t.colors.input,
75
+ },
76
+ sheetHead: {
77
+ flexDirection: 'row',
78
+ alignItems: 'center',
79
+ gap: t.space[2],
80
+ paddingLeft: t.layout.screenX,
81
+ paddingRight: t.space[1],
82
+ minHeight: t.controlSize.md,
83
+ },
84
+ sheetTitle: { flex: 1 },
85
+ close: {
86
+ width: t.minTouchSize,
87
+ height: t.minTouchSize,
88
+ alignItems: 'center',
89
+ justifyContent: 'center',
90
+ },
91
+ sheetBody: {
92
+ gap: t.space[4],
93
+ paddingHorizontal: t.layout.screenX,
94
+ paddingTop: t.space[2],
95
+ },
96
+ }));
97
+
98
+ export function EditorTemplate({
99
+ preview,
100
+ tools,
101
+ sheet,
102
+ closeLabel = '닫기',
103
+ bottomInset = 0,
104
+ }: EditorTemplateProps) {
105
+ const t = useTheme();
106
+ const s = useStyles();
107
+ return (
108
+ <View style={s.root}>
109
+ <View style={s.canvas}>
110
+ <View style={s.preview}>{preview}</View>
111
+ </View>
112
+ <View
113
+ style={[
114
+ s.tools,
115
+ { paddingBottom: t.layout.footerBottom + bottomInset },
116
+ ]}
117
+ >
118
+ {tools.map((tool) => (
119
+ <Button
120
+ key={tool.key}
121
+ variant="outline"
122
+ onPress={tool.onPress}
123
+ disabled={tool.disabled}
124
+ style={s.tool}
125
+ >
126
+ {tool.label}
127
+ </Button>
128
+ ))}
129
+ </View>
130
+ {sheet ? (
131
+ <View style={s.sheet}>
132
+ <View style={s.grab} />
133
+ <View style={s.sheetHead}>
134
+ <Text
135
+ variant="bodyLg"
136
+ weight="semibold"
137
+ accessibilityRole="header"
138
+ style={s.sheetTitle}
139
+ >
140
+ {sheet.title}
141
+ </Text>
142
+ {sheet.headerAction}
143
+ <PressableBase
144
+ onPress={sheet.onClose}
145
+ accessibilityLabel={closeLabel}
146
+ style={s.close}
147
+ >
148
+ <CloseGlyph color={t.colors.foreground} size={t.iconSize.md} />
149
+ </PressableBase>
150
+ </View>
151
+ <ScrollView
152
+ keyboardShouldPersistTaps="handled"
153
+ contentContainerStyle={[
154
+ s.sheetBody,
155
+ { paddingBottom: t.layout.footerBottom + bottomInset },
156
+ ]}
157
+ >
158
+ {sheet.children}
159
+ </ScrollView>
160
+ </View>
161
+ ) : null}
162
+ </View>
163
+ );
164
+ }
@@ -0,0 +1,49 @@
1
+ import type { ReactNode } from 'react';
2
+ import { View } from 'react-native';
3
+ import { Text } from '../components/text/text';
4
+ import { makeStyles } from '../utils/styles';
5
+
6
+ export type GroupProps = {
7
+ title?: string;
8
+ aside?: string;
9
+ children: ReactNode;
10
+ };
11
+
12
+ const useStyles = makeStyles((t) => ({
13
+ root: { gap: t.layout.groupGap },
14
+ head: {
15
+ flexDirection: 'row',
16
+ alignItems: 'center',
17
+ justifyContent: 'space-between',
18
+ gap: t.space[2],
19
+ },
20
+ title: { flexShrink: 1 },
21
+ }));
22
+
23
+ export function Group({ title, aside, children }: GroupProps) {
24
+ const s = useStyles();
25
+ return (
26
+ <View style={s.root}>
27
+ {title || aside ? (
28
+ <View style={s.head}>
29
+ {title ? (
30
+ <Text
31
+ variant="bodyLg"
32
+ weight="semibold"
33
+ accessibilityRole="header"
34
+ style={s.title}
35
+ >
36
+ {title}
37
+ </Text>
38
+ ) : null}
39
+ {aside ? (
40
+ <Text variant="label" tone="muted">
41
+ {aside}
42
+ </Text>
43
+ ) : null}
44
+ </View>
45
+ ) : null}
46
+ {children}
47
+ </View>
48
+ );
49
+ }
@@ -0,0 +1,28 @@
1
+ export {
2
+ DetailTemplate,
3
+ DashboardTemplate,
4
+ FormTemplate,
5
+ MenuTemplate,
6
+ } from './scroll-templates';
7
+ export type {
8
+ DashboardTemplateProps,
9
+ DetailTemplateProps,
10
+ FormTemplateProps,
11
+ MenuTemplateProps,
12
+ } from './scroll-templates';
13
+ export { EditorTemplate } from './editor-template';
14
+ export type {
15
+ EditorSheet,
16
+ EditorTemplateProps,
17
+ EditorTool,
18
+ } from './editor-template';
19
+ export { ListTemplate } from './list-template';
20
+ export type { ListTemplateProps, ListTemplateStatus } from './list-template';
21
+ export { ResultTemplate } from './result-template';
22
+ export type { ResultPreview, ResultTemplateProps } from './result-template';
23
+ export { SystemTemplate } from './system-template';
24
+ export type { SystemTemplateProps } from './system-template';
25
+ export { ListSection } from './parts';
26
+ export { Group } from './group';
27
+ export type { GroupProps } from './group';
28
+ export type { NoticeReason, TemplateAction, TemplateNotice } from './parts';
@@ -0,0 +1,124 @@
1
+ import type { ReactElement, ReactNode } from 'react';
2
+ import {
3
+ FlatList,
4
+ RefreshControl,
5
+ View,
6
+ type ListRenderItem,
7
+ } from 'react-native';
8
+ import { EmptyState } from '../components/empty-state/empty-state';
9
+ import { Skeleton } from '../components/skeleton/skeleton';
10
+ import { useTheme } from '../theme/theme-provider';
11
+ import { makeStyles } from '../utils/styles';
12
+ import type { TemplateAction } from './parts';
13
+
14
+ export type ListTemplateStatus = 'ready' | 'loading' | 'error';
15
+
16
+ export type ListTemplateProps<T> = {
17
+ data: readonly T[];
18
+ renderItem: ListRenderItem<T>;
19
+ keyExtractor: (item: T) => string;
20
+ status?: ListTemplateStatus;
21
+ filters?: ReactNode;
22
+ pinned?: ReactNode;
23
+ empty: { title: string; action?: Pick<TemplateAction, 'label' | 'onPress'> };
24
+ error?: { title: string; onRetry: () => void };
25
+ refreshing?: boolean;
26
+ onRefresh?: () => void;
27
+ onEndReached?: () => void;
28
+ selectionBar?: ReactElement | null;
29
+ bottomInset?: number;
30
+ };
31
+
32
+ const useStyles = makeStyles((t) => ({
33
+ root: { flex: 1, backgroundColor: t.colors.background },
34
+ filters: {
35
+ gap: t.layout.groupGap,
36
+ paddingTop: t.space[2],
37
+ paddingBottom: t.space[2],
38
+ paddingHorizontal: t.layout.screenX,
39
+ },
40
+ loading: {
41
+ gap: t.space[4],
42
+ paddingTop: t.space[2],
43
+ paddingHorizontal: t.layout.screenX,
44
+ },
45
+ grow: { flexGrow: 1 },
46
+ }));
47
+
48
+ export function ListTemplate<T>({
49
+ data,
50
+ renderItem,
51
+ keyExtractor,
52
+ status = 'ready',
53
+ filters,
54
+ pinned,
55
+ empty,
56
+ error,
57
+ refreshing = false,
58
+ onRefresh,
59
+ onEndReached,
60
+ selectionBar,
61
+ bottomInset = 0,
62
+ }: ListTemplateProps<T>) {
63
+ const t = useTheme();
64
+ const s = useStyles();
65
+ const head = filters ? <View style={s.filters}>{filters}</View> : null;
66
+
67
+ if (status === 'loading') {
68
+ return (
69
+ <View style={s.root}>
70
+ {head}
71
+ <View style={s.loading}>
72
+ {[0, 1, 2, 3, 4].map((i) => (
73
+ <Skeleton key={i} height={t.listRow.oneLine} />
74
+ ))}
75
+ </View>
76
+ </View>
77
+ );
78
+ }
79
+
80
+ if (status === 'error' && error) {
81
+ return (
82
+ <View style={s.root}>
83
+ {head}
84
+ <EmptyState
85
+ variant="error"
86
+ title={error.title}
87
+ action={{ label: '다시 시도', onPress: error.onRetry }}
88
+ />
89
+ </View>
90
+ );
91
+ }
92
+
93
+ return (
94
+ <View style={s.root}>
95
+ <FlatList
96
+ data={data}
97
+ renderItem={renderItem}
98
+ keyExtractor={keyExtractor}
99
+ ListHeaderComponent={
100
+ head || pinned ? (
101
+ <>
102
+ {head}
103
+ {pinned}
104
+ </>
105
+ ) : undefined
106
+ }
107
+ ListEmptyComponent={
108
+ <EmptyState title={empty.title} action={empty.action} />
109
+ }
110
+ contentContainerStyle={[
111
+ data.length === 0 ? s.grow : null,
112
+ selectionBar ? null : { paddingBottom: bottomInset },
113
+ ]}
114
+ refreshControl={
115
+ onRefresh ? (
116
+ <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
117
+ ) : undefined
118
+ }
119
+ onEndReached={onEndReached}
120
+ />
121
+ {selectionBar}
122
+ </View>
123
+ );
124
+ }
@@ -0,0 +1,138 @@
1
+ import type { ReactNode } from 'react';
2
+ import { View } from 'react-native';
3
+ import { Button } from '../components/button/button';
4
+ import { Text } from '../components/text/text';
5
+ import { AlertGlyph } from '../internal/glyphs';
6
+ import { useTheme } from '../theme/theme-provider';
7
+ import { makeStyles } from '../utils/styles';
8
+
9
+ export type TemplateAction = {
10
+ label: string;
11
+ onPress: () => void;
12
+ disabled?: boolean;
13
+ loading?: boolean;
14
+ };
15
+
16
+ export type NoticeReason = 'error' | 'money' | 'irreversible' | 'legal';
17
+
18
+ export type TemplateNotice = { reason: NoticeReason; text: string };
19
+
20
+ const useStyles = makeStyles((t) => ({
21
+ bar: {
22
+ gap: t.layout.itemGap,
23
+ paddingHorizontal: t.layout.screenX,
24
+ paddingTop: t.layout.footerTop,
25
+ backgroundColor: t.colors.background,
26
+ },
27
+ notice: {
28
+ flexDirection: 'row',
29
+ alignItems: 'flex-start',
30
+ gap: t.space[2],
31
+ marginHorizontal: t.layout.screenX,
32
+ paddingVertical: t.space[3],
33
+ paddingHorizontal: t.space[4],
34
+ borderRadius: t.radius.md,
35
+ backgroundColor: t.colors.muted,
36
+ },
37
+ noticeText: { flex: 1 },
38
+ section: { paddingBottom: t.space[2] },
39
+ sectionHead: {
40
+ flexDirection: 'row',
41
+ alignItems: 'center',
42
+ justifyContent: 'space-between',
43
+ paddingTop: t.space[4],
44
+ paddingBottom: t.space[2],
45
+ paddingHorizontal: t.layout.screenX,
46
+ },
47
+ inset: { paddingHorizontal: t.layout.screenX },
48
+ }));
49
+
50
+ export function BottomBar({
51
+ primaryAction,
52
+ secondaryAction,
53
+ bottomInset = 0,
54
+ }: {
55
+ primaryAction: TemplateAction;
56
+ secondaryAction?: TemplateAction;
57
+ bottomInset?: number;
58
+ }) {
59
+ const t = useTheme();
60
+ const s = useStyles();
61
+ return (
62
+ <View
63
+ style={[s.bar, { paddingBottom: t.layout.footerBottom + bottomInset }]}
64
+ >
65
+ <Button
66
+ size="lg"
67
+ onPress={primaryAction.onPress}
68
+ disabled={primaryAction.disabled}
69
+ loading={primaryAction.loading}
70
+ >
71
+ {primaryAction.label}
72
+ </Button>
73
+ {secondaryAction ? (
74
+ <Button
75
+ variant="ghost"
76
+ onPress={secondaryAction.onPress}
77
+ disabled={secondaryAction.disabled}
78
+ loading={secondaryAction.loading}
79
+ >
80
+ {secondaryAction.label}
81
+ </Button>
82
+ ) : null}
83
+ </View>
84
+ );
85
+ }
86
+
87
+ export function Notice({ notice }: { notice: TemplateNotice }) {
88
+ const t = useTheme();
89
+ const s = useStyles();
90
+ const danger = notice.reason === 'error';
91
+ return (
92
+ <View accessibilityRole={danger ? 'alert' : undefined} style={s.notice}>
93
+ <AlertGlyph
94
+ color={danger ? t.colors.danger : t.colors.mutedForeground}
95
+ size={t.iconSize.md}
96
+ />
97
+ <Text
98
+ variant="label"
99
+ tone={danger ? 'danger' : 'default'}
100
+ style={s.noticeText}
101
+ >
102
+ {notice.text}
103
+ </Text>
104
+ </View>
105
+ );
106
+ }
107
+
108
+ export function ListSection({
109
+ title,
110
+ action,
111
+ inset,
112
+ children,
113
+ }: {
114
+ title?: string;
115
+ action?: ReactNode;
116
+ inset?: boolean;
117
+ children: ReactNode;
118
+ }) {
119
+ const s = useStyles();
120
+ return (
121
+ <View style={s.section}>
122
+ {title ? (
123
+ <View style={s.sectionHead}>
124
+ <Text
125
+ variant="label"
126
+ tone="muted"
127
+ weight="semibold"
128
+ accessibilityRole="header"
129
+ >
130
+ {title}
131
+ </Text>
132
+ {action}
133
+ </View>
134
+ ) : null}
135
+ {inset ? <View style={s.inset}>{children}</View> : children}
136
+ </View>
137
+ );
138
+ }