@ezpg/app-ui 0.6.0 → 0.8.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 (61) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/lib/module/blocks/color-swatch-picker/color-swatch-picker.js +22 -5
  3. package/lib/module/blocks/color-swatch-picker/color-swatch-picker.js.map +1 -1
  4. package/lib/module/blocks/copy-variant-picker/copy-variant-picker.js +2 -3
  5. package/lib/module/blocks/copy-variant-picker/copy-variant-picker.js.map +1 -1
  6. package/lib/module/blocks/internal/card.js +2 -3
  7. package/lib/module/blocks/internal/card.js.map +1 -1
  8. package/lib/module/blocks/logo-candidate-grid/logo-candidate-grid.js +1 -5
  9. package/lib/module/blocks/logo-candidate-grid/logo-candidate-grid.js.map +1 -1
  10. package/lib/module/blocks/plan-card/plan-card.js +2 -3
  11. package/lib/module/blocks/plan-card/plan-card.js.map +1 -1
  12. package/lib/module/components/calendar/calendar.js +201 -0
  13. package/lib/module/components/calendar/calendar.js.map +1 -0
  14. package/lib/module/components/calendar/index.js +4 -0
  15. package/lib/module/components/calendar/index.js.map +1 -0
  16. package/lib/module/components/chip/chip.js +8 -7
  17. package/lib/module/components/chip/chip.js.map +1 -1
  18. package/lib/module/components/field/field.js +1 -3
  19. package/lib/module/components/field/field.js.map +1 -1
  20. package/lib/module/components/text/text.js +3 -0
  21. package/lib/module/components/text/text.js.map +1 -1
  22. package/lib/module/index.js +1 -0
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/templates/result-template.js +1 -1
  25. package/lib/module/templates/result-template.js.map +1 -1
  26. package/lib/module/theme/base.js +10 -4
  27. package/lib/module/theme/base.js.map +1 -1
  28. package/lib/typescript/src/blocks/color-swatch-picker/color-swatch-picker.d.ts.map +1 -1
  29. package/lib/typescript/src/blocks/copy-variant-picker/copy-variant-picker.d.ts.map +1 -1
  30. package/lib/typescript/src/blocks/internal/card.d.ts.map +1 -1
  31. package/lib/typescript/src/blocks/logo-candidate-grid/logo-candidate-grid.d.ts.map +1 -1
  32. package/lib/typescript/src/blocks/plan-card/plan-card.d.ts.map +1 -1
  33. package/lib/typescript/src/components/calendar/calendar.d.ts +20 -0
  34. package/lib/typescript/src/components/calendar/calendar.d.ts.map +1 -0
  35. package/lib/typescript/src/components/calendar/index.d.ts +2 -0
  36. package/lib/typescript/src/components/calendar/index.d.ts.map +1 -0
  37. package/lib/typescript/src/components/chip/chip.d.ts.map +1 -1
  38. package/lib/typescript/src/components/field/field.d.ts.map +1 -1
  39. package/lib/typescript/src/components/text/text.d.ts +1 -1
  40. package/lib/typescript/src/components/text/text.d.ts.map +1 -1
  41. package/lib/typescript/src/index.d.ts +1 -0
  42. package/lib/typescript/src/index.d.ts.map +1 -1
  43. package/lib/typescript/src/theme/base.d.ts +10 -4
  44. package/lib/typescript/src/theme/base.d.ts.map +1 -1
  45. package/lib/typescript/src/types/index.d.ts +3 -0
  46. package/lib/typescript/src/types/index.d.ts.map +1 -1
  47. package/package.json +1 -1
  48. package/src/blocks/color-swatch-picker/color-swatch-picker.tsx +20 -4
  49. package/src/blocks/copy-variant-picker/copy-variant-picker.tsx +2 -3
  50. package/src/blocks/internal/card.tsx +2 -3
  51. package/src/blocks/logo-candidate-grid/logo-candidate-grid.tsx +1 -2
  52. package/src/blocks/plan-card/plan-card.tsx +2 -3
  53. package/src/components/calendar/calendar.tsx +215 -0
  54. package/src/components/calendar/index.ts +7 -0
  55. package/src/components/chip/chip.tsx +5 -6
  56. package/src/components/field/field.tsx +1 -3
  57. package/src/components/text/text.tsx +3 -1
  58. package/src/index.tsx +1 -0
  59. package/src/templates/result-template.tsx +1 -1
  60. package/src/theme/base.ts +10 -4
  61. package/src/types/index.ts +3 -0
@@ -0,0 +1,215 @@
1
+ import { useState } from 'react';
2
+ import { Text, View, type StyleProp, type ViewStyle } from 'react-native';
3
+ import { PressableBase } from '../../internal/pressable-base';
4
+ import { useTheme } from '../../theme/theme-provider';
5
+ import { makeStyles } from '../../utils/styles';
6
+
7
+ export type CalendarDate = { date: string; label?: string; disabled?: boolean };
8
+ export type CalendarValue = { start?: string; end?: string };
9
+
10
+ export type CalendarProps = {
11
+ days: readonly CalendarDate[];
12
+ mode?: 'single' | 'range';
13
+ value: CalendarValue;
14
+ onValueChange: (value: CalendarValue) => void;
15
+ style?: StyleProp<ViewStyle>;
16
+ };
17
+
18
+ const WEEKDAYS = ['일', '월', '화', '수', '목', '금', '토'];
19
+ const DAY_MS = 86_400_000;
20
+
21
+ function utc(iso: string): number {
22
+ const [y, m, d] = iso.split('-').map(Number);
23
+ return Date.UTC(y!, m! - 1, d!);
24
+ }
25
+
26
+ export function nightsBetween(start: string, end: string): number {
27
+ return Math.round((utc(end) - utc(start)) / DAY_MS);
28
+ }
29
+
30
+ function weekday(iso: string): number {
31
+ return new Date(utc(iso)).getUTCDay();
32
+ }
33
+
34
+ const useStyles = makeStyles((t) => ({
35
+ root: { gap: t.space[3] },
36
+ head: {
37
+ flexDirection: 'row',
38
+ alignItems: 'center',
39
+ justifyContent: 'space-between',
40
+ },
41
+ arrow: { width: 44, alignItems: 'center', justifyContent: 'center' },
42
+ month: {
43
+ fontFamily: t.fontFamily,
44
+ fontSize: t.typography.title.fontSize,
45
+ lineHeight: t.typography.title.lineHeight,
46
+ fontWeight: '700',
47
+ color: t.colors.foreground,
48
+ },
49
+ grid: { flexDirection: 'row', flexWrap: 'wrap' },
50
+ cell: { width: `${100 / 7}%`, padding: t.space[1] / 2 },
51
+ weekday: {
52
+ textAlign: 'center',
53
+ fontFamily: t.fontFamily,
54
+ fontSize: t.typography.caption.fontSize,
55
+ lineHeight: t.typography.caption.lineHeight,
56
+ color: t.colors.mutedForeground,
57
+ },
58
+ day: {
59
+ minHeight: 48,
60
+ alignItems: 'center',
61
+ justifyContent: 'center',
62
+ borderRadius: t.radius.sm,
63
+ backgroundColor: t.colors.muted,
64
+ },
65
+ edge: { backgroundColor: t.colors.primary },
66
+ inside: { backgroundColor: t.colors.border },
67
+ off: { backgroundColor: 'transparent' },
68
+ struck: { textDecorationLine: 'line-through' },
69
+ number: {
70
+ fontFamily: t.fontFamily,
71
+ fontSize: t.typography.label.fontSize,
72
+ lineHeight: t.typography.label.lineHeight,
73
+ fontVariant: ['tabular-nums'],
74
+ },
75
+ sub: {
76
+ fontFamily: t.fontFamily,
77
+ fontSize: t.typography.caption.fontSize,
78
+ lineHeight: t.typography.caption.lineHeight,
79
+ },
80
+ }));
81
+
82
+ export function Calendar({
83
+ days,
84
+ mode = 'single',
85
+ value,
86
+ onValueChange,
87
+ style,
88
+ }: CalendarProps) {
89
+ const t = useTheme();
90
+ const s = useStyles();
91
+ const months = [...new Set(days.map((d) => d.date.slice(0, 7)))];
92
+ const [page, setPage] = useState(() =>
93
+ Math.max(0, months.indexOf((value.start ?? '').slice(0, 7)))
94
+ );
95
+ const month = months[page] ?? months[0] ?? '';
96
+ const shown = days.filter((d) => d.date.startsWith(month));
97
+ const [year, monthNo] = month.split('-').map(Number);
98
+ const openUntil = (start: string, end: string) => {
99
+ const nights = days.filter((d) => d.date >= start && d.date < end);
100
+ return (
101
+ nights.length === nightsBetween(start, end) &&
102
+ nights.every((d) => !d.disabled)
103
+ );
104
+ };
105
+ const canEnd = (iso: string) =>
106
+ mode === 'range' &&
107
+ !!value.start &&
108
+ !value.end &&
109
+ iso > value.start &&
110
+ openUntil(value.start, iso);
111
+ const pick = (day: CalendarDate) => {
112
+ if (mode === 'single') onValueChange({ start: day.date });
113
+ else if (canEnd(day.date))
114
+ onValueChange({ start: value.start, end: day.date });
115
+ else if (!day.disabled) onValueChange({ start: day.date });
116
+ };
117
+ const look = (iso: string) => {
118
+ if (iso === value.start || iso === value.end) return 'edge';
119
+ if (value.start && value.end && iso > value.start && iso < value.end)
120
+ return 'inside';
121
+ return 'none';
122
+ };
123
+ const blanks = shown.length ? weekday(shown[0]!.date) : 0;
124
+
125
+ return (
126
+ <View style={[s.root, style]}>
127
+ <View style={s.head}>
128
+ <PressableBase
129
+ style={s.arrow}
130
+ accessibilityRole="button"
131
+ accessibilityLabel="이전 달"
132
+ disabled={page <= 0}
133
+ onPress={() => setPage(page - 1)}
134
+ >
135
+ <Text
136
+ style={[s.month, page <= 0 && { color: t.colors.mutedForeground }]}
137
+ >
138
+ ‹
139
+ </Text>
140
+ </PressableBase>
141
+ <Text style={s.month}>{`${year}년 ${monthNo}월`}</Text>
142
+ <PressableBase
143
+ style={s.arrow}
144
+ accessibilityRole="button"
145
+ accessibilityLabel="다음 달"
146
+ disabled={page >= months.length - 1}
147
+ onPress={() => setPage(page + 1)}
148
+ >
149
+ <Text
150
+ style={[
151
+ s.month,
152
+ page >= months.length - 1 && { color: t.colors.mutedForeground },
153
+ ]}
154
+ >
155
+ ›
156
+ </Text>
157
+ </PressableBase>
158
+ </View>
159
+ <View style={s.grid}>
160
+ {WEEKDAYS.map((w) => (
161
+ <View key={w} style={s.cell}>
162
+ <Text style={s.weekday}>{w}</Text>
163
+ </View>
164
+ ))}
165
+ {Array.from({ length: blanks }, (_, i) => (
166
+ <View key={`blank-${i}`} style={s.cell} />
167
+ ))}
168
+ {shown.map((day) => {
169
+ const state = look(day.date);
170
+ const blocked = !!day.disabled && !canEnd(day.date);
171
+ const color =
172
+ state === 'edge'
173
+ ? t.colors.primaryForeground
174
+ : blocked
175
+ ? t.colors.mutedForeground
176
+ : t.colors.foreground;
177
+ const [, m, d] = day.date.split('-').map(Number);
178
+ return (
179
+ <View key={day.date} style={s.cell}>
180
+ <PressableBase
181
+ style={[
182
+ s.day,
183
+ state === 'edge' && s.edge,
184
+ state === 'inside' && s.inside,
185
+ blocked && s.off,
186
+ ]}
187
+ accessibilityRole="button"
188
+ accessibilityLabel={[
189
+ `${m}월 ${d}일`,
190
+ `${WEEKDAYS[weekday(day.date)]}요일`,
191
+ day.label,
192
+ ]
193
+ .filter(Boolean)
194
+ .join(' ')}
195
+ accessibilityState={{
196
+ selected: state === 'edge',
197
+ disabled: blocked,
198
+ }}
199
+ disabled={blocked}
200
+ onPress={() => pick(day)}
201
+ >
202
+ <Text style={[s.number, { color }, blocked && s.struck]}>
203
+ {d}
204
+ </Text>
205
+ {day.label ? (
206
+ <Text style={[s.sub, { color }]}>{day.label}</Text>
207
+ ) : null}
208
+ </PressableBase>
209
+ </View>
210
+ );
211
+ })}
212
+ </View>
213
+ </View>
214
+ );
215
+ }
@@ -0,0 +1,7 @@
1
+ export {
2
+ Calendar,
3
+ nightsBetween,
4
+ type CalendarDate,
5
+ type CalendarProps,
6
+ type CalendarValue,
7
+ } from './calendar';
@@ -1,4 +1,5 @@
1
1
  import { Text, View } from 'react-native';
2
+ import { CheckGlyph } from '../../internal/glyphs';
2
3
  import { renderIcon, type IconSlot } from '../../internal/icon';
3
4
  import {
4
5
  PressableBase,
@@ -25,13 +26,10 @@ const useStyles = makeStyles((t) => ({
25
26
  height: HEIGHT,
26
27
  paddingHorizontal: t.space[3],
27
28
  borderRadius: t.radius.full,
28
- borderWidth: 1,
29
- borderColor: t.colors.border,
30
- backgroundColor: t.colors.background,
29
+ backgroundColor: t.colors.muted,
31
30
  },
32
31
  selected: {
33
- backgroundColor: t.colors.primary,
34
- borderColor: t.colors.primary,
32
+ backgroundColor: t.colors.primaryContainer,
35
33
  },
36
34
  label: {
37
35
  fontFamily: t.fontFamily,
@@ -54,7 +52,7 @@ export function Chip({
54
52
  }: ChipProps) {
55
53
  const t = useTheme();
56
54
  const s = useStyles();
57
- const color = selected ? t.colors.primaryForeground : t.colors.foreground;
55
+ const color = selected ? t.colors.primary : t.colors.foreground;
58
56
  return (
59
57
  <PressableBase
60
58
  {...rest}
@@ -66,6 +64,7 @@ export function Chip({
66
64
  touchHeight={HEIGHT}
67
65
  style={[s.base, selected && s.selected, style]}
68
66
  >
67
+ {selected ? <CheckGlyph color={color} size={t.iconSize.sm} /> : null}
69
68
  {leftIcon ? (
70
69
  <View>{renderIcon(leftIcon, { color, size: t.iconSize.sm })}</View>
71
70
  ) : null}
@@ -44,9 +44,7 @@ const useStyles = makeStyles((t) => ({
44
44
  backgroundColor: t.colors.background,
45
45
  },
46
46
  focused: {
47
- borderWidth: 2,
48
- paddingHorizontal: t.space[4] - 1,
49
- borderColor: t.colors.primary,
47
+ borderColor: t.colors.primaryEdge,
50
48
  },
51
49
  invalid: {
52
50
  borderWidth: 2,
@@ -4,7 +4,8 @@ import type { TypographyVariant } from '../../types';
4
4
  import { useTheme } from '../../theme/theme-provider';
5
5
  import { makeStyles } from '../../utils/styles';
6
6
 
7
- export type TextTone = 'default' | 'muted' | 'warning' | 'danger' | 'success';
7
+ export type TextTone =
8
+ 'default' | 'muted' | 'primary' | 'warning' | 'danger' | 'success';
8
9
 
9
10
  export type TextProps = RNTextProps & {
10
11
  ref?: Ref<ComponentRef<typeof RNText>>;
@@ -25,6 +26,7 @@ const useStyles = makeStyles((t) => ({
25
26
  display: t.typography.display,
26
27
  default: { color: t.colors.foreground },
27
28
  muted: { color: t.colors.mutedForeground },
29
+ primary: { color: t.colors.primary },
28
30
  warning: { color: t.colors.warningText },
29
31
  danger: { color: t.colors.danger },
30
32
  success: { color: t.colors.success },
package/src/index.tsx CHANGED
@@ -24,6 +24,7 @@ export { contrastRatio, pickForeground } from './utils/color';
24
24
  export * from './components/button';
25
25
  export * from './components/checkbox';
26
26
  export * from './components/chip';
27
+ export * from './components/calendar';
27
28
  export * from './components/chip-group';
28
29
  export * from './components/dialog';
29
30
  export * from './components/divider';
@@ -68,7 +68,7 @@ const useStyles = makeStyles((t) => ({
68
68
  borderRadius: t.radius.full,
69
69
  backgroundColor: t.colors.input,
70
70
  },
71
- dotOn: { width: t.space[5], backgroundColor: t.colors.foreground },
71
+ dotOn: { width: t.space[5], backgroundColor: t.colors.primary },
72
72
  progress: { flex: 1, paddingHorizontal: t.layout.screenX },
73
73
  notice: { paddingBottom: t.layout.groupGap },
74
74
  viewer: { flex: 1, backgroundColor: t.colors.background },
package/src/theme/base.ts CHANGED
@@ -7,8 +7,11 @@ export const semantic = {
7
7
  border: '#e5e5e5',
8
8
  input: '#d4d4d4',
9
9
  ring: '#a3a3a3',
10
- primary: '#171717',
11
- primaryForeground: '#fafafa',
10
+ primary: '#4d291c',
11
+ primaryForeground: '#ffffff',
12
+ primaryTint: '#f8f3f2',
13
+ primaryContainer: '#e3d2cb',
14
+ primaryEdge: '#946b5c',
12
15
  danger: '#b91c1c',
13
16
  dangerForeground: '#ffffff',
14
17
  success: '#15803d',
@@ -27,8 +30,11 @@ export const semantic = {
27
30
  border: '#262626',
28
31
  input: '#404040',
29
32
  ring: '#737373',
30
- primary: '#fafafa',
31
- primaryForeground: '#171717',
33
+ primary: '#caada3',
34
+ primaryForeground: '#2b1710',
35
+ primaryTint: '#2b1710',
36
+ primaryContainer: '#3a1f15',
37
+ primaryEdge: '#946b5c',
32
38
  danger: '#f87171',
33
39
  dangerForeground: '#0a0a0a',
34
40
  success: '#4ade80',
@@ -12,6 +12,9 @@ export type ThemeColors = {
12
12
  ring: string;
13
13
  primary: string;
14
14
  primaryForeground: string;
15
+ primaryTint: string;
16
+ primaryContainer: string;
17
+ primaryEdge: string;
15
18
  danger: string;
16
19
  dangerForeground: string;
17
20
  success: string;