@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.
- package/CHANGELOG.md +15 -0
- package/lib/module/blocks/color-swatch-picker/color-swatch-picker.js +22 -5
- package/lib/module/blocks/color-swatch-picker/color-swatch-picker.js.map +1 -1
- package/lib/module/blocks/copy-variant-picker/copy-variant-picker.js +2 -3
- package/lib/module/blocks/copy-variant-picker/copy-variant-picker.js.map +1 -1
- package/lib/module/blocks/internal/card.js +2 -3
- package/lib/module/blocks/internal/card.js.map +1 -1
- package/lib/module/blocks/logo-candidate-grid/logo-candidate-grid.js +1 -5
- package/lib/module/blocks/logo-candidate-grid/logo-candidate-grid.js.map +1 -1
- package/lib/module/blocks/plan-card/plan-card.js +2 -3
- package/lib/module/blocks/plan-card/plan-card.js.map +1 -1
- package/lib/module/components/calendar/calendar.js +201 -0
- package/lib/module/components/calendar/calendar.js.map +1 -0
- package/lib/module/components/calendar/index.js +4 -0
- package/lib/module/components/calendar/index.js.map +1 -0
- package/lib/module/components/chip/chip.js +8 -7
- package/lib/module/components/chip/chip.js.map +1 -1
- package/lib/module/components/field/field.js +1 -3
- package/lib/module/components/field/field.js.map +1 -1
- package/lib/module/components/text/text.js +3 -0
- package/lib/module/components/text/text.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/templates/result-template.js +1 -1
- package/lib/module/templates/result-template.js.map +1 -1
- package/lib/module/theme/base.js +10 -4
- package/lib/module/theme/base.js.map +1 -1
- package/lib/typescript/src/blocks/color-swatch-picker/color-swatch-picker.d.ts.map +1 -1
- package/lib/typescript/src/blocks/copy-variant-picker/copy-variant-picker.d.ts.map +1 -1
- package/lib/typescript/src/blocks/internal/card.d.ts.map +1 -1
- package/lib/typescript/src/blocks/logo-candidate-grid/logo-candidate-grid.d.ts.map +1 -1
- package/lib/typescript/src/blocks/plan-card/plan-card.d.ts.map +1 -1
- package/lib/typescript/src/components/calendar/calendar.d.ts +20 -0
- package/lib/typescript/src/components/calendar/calendar.d.ts.map +1 -0
- package/lib/typescript/src/components/calendar/index.d.ts +2 -0
- package/lib/typescript/src/components/calendar/index.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/chip.d.ts.map +1 -1
- package/lib/typescript/src/components/field/field.d.ts.map +1 -1
- package/lib/typescript/src/components/text/text.d.ts +1 -1
- package/lib/typescript/src/components/text/text.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/base.d.ts +10 -4
- package/lib/typescript/src/theme/base.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts +3 -0
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/blocks/color-swatch-picker/color-swatch-picker.tsx +20 -4
- package/src/blocks/copy-variant-picker/copy-variant-picker.tsx +2 -3
- package/src/blocks/internal/card.tsx +2 -3
- package/src/blocks/logo-candidate-grid/logo-candidate-grid.tsx +1 -2
- package/src/blocks/plan-card/plan-card.tsx +2 -3
- package/src/components/calendar/calendar.tsx +215 -0
- package/src/components/calendar/index.ts +7 -0
- package/src/components/chip/chip.tsx +5 -6
- package/src/components/field/field.tsx +1 -3
- package/src/components/text/text.tsx +3 -1
- package/src/index.tsx +1 -0
- package/src/templates/result-template.tsx +1 -1
- package/src/theme/base.ts +10 -4
- 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
|
+
}
|
|
@@ -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
|
-
|
|
29
|
-
borderColor: t.colors.border,
|
|
30
|
-
backgroundColor: t.colors.background,
|
|
29
|
+
backgroundColor: t.colors.muted,
|
|
31
30
|
},
|
|
32
31
|
selected: {
|
|
33
|
-
backgroundColor: t.colors.
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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: '#
|
|
11
|
-
primaryForeground: '#
|
|
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: '#
|
|
31
|
-
primaryForeground: '#
|
|
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',
|
package/src/types/index.ts
CHANGED