cpk-ui 0.0.1

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 (69) hide show
  1. package/README.md +27 -0
  2. package/components/modals/AlertDialog/AlertDialog.js +80 -0
  3. package/components/modals/AlertDialog/AlertDialog.stories.tsx +80 -0
  4. package/components/modals/AlertDialog/AlertDialog.tsx +194 -0
  5. package/components/modals/Snackbar/Snackbar.js +94 -0
  6. package/components/modals/Snackbar/Snackbar.stories.tsx +98 -0
  7. package/components/modals/Snackbar/Snackbar.tsx +174 -0
  8. package/components/modals/Snackbar/const.js +5 -0
  9. package/components/modals/Snackbar/const.ts +4 -0
  10. package/components/uis/Accordion/Accordion.js +11 -0
  11. package/components/uis/Accordion/Accordion.stories.tsx +38 -0
  12. package/components/uis/Accordion/Accordion.test.tsx +128 -0
  13. package/components/uis/Accordion/Accordion.tsx +58 -0
  14. package/components/uis/Accordion/AccordionItem.js +102 -0
  15. package/components/uis/Accordion/AccordionItem.tsx +213 -0
  16. package/components/uis/Button/Button.js +161 -0
  17. package/components/uis/Button/Button.stories.tsx +81 -0
  18. package/components/uis/Button/Button.test.tsx +560 -0
  19. package/components/uis/Button/Button.tsx +335 -0
  20. package/components/uis/Checkbox/Checkbox.js +70 -0
  21. package/components/uis/Checkbox/Checkbox.stories.tsx +46 -0
  22. package/components/uis/Checkbox/Checkbox.test.tsx +139 -0
  23. package/components/uis/Checkbox/Checkbox.tsx +150 -0
  24. package/components/uis/Icon/Icon.js +3780 -0
  25. package/components/uis/Icon/Icon.stories.tsx +45 -0
  26. package/components/uis/Icon/Icon.test.tsx +19 -0
  27. package/components/uis/Icon/Icon.tsx +3800 -0
  28. package/components/uis/Icon/Pretendard-Bold.otf +0 -0
  29. package/components/uis/Icon/Pretendard-Regular.otf +0 -0
  30. package/components/uis/Icon/Pretendard-Thin.otf +0 -0
  31. package/components/uis/Icon/cpk.ttf +0 -0
  32. package/components/uis/Icon/selection.json +1 -0
  33. package/components/uis/IconButton/IconButton.js +120 -0
  34. package/components/uis/IconButton/IconButton.test.tsx +165 -0
  35. package/components/uis/IconButton/IconButton.tsx +252 -0
  36. package/components/uis/LoadingIndicator/LoadingIndicator.js +24 -0
  37. package/components/uis/LoadingIndicator/LoadingIndicator.tsx +79 -0
  38. package/components/uis/Rating/Rating.js +53 -0
  39. package/components/uis/Rating/Rating.test.tsx +72 -0
  40. package/components/uis/Rating/Rating.tsx +155 -0
  41. package/components/uis/StatusbarBrightness/StatusBarBrightness.js +13 -0
  42. package/components/uis/StatusbarBrightness/StatusBarBrightness.test.tsx +41 -0
  43. package/components/uis/StatusbarBrightness/StatusBarBrightness.tsx +17 -0
  44. package/components/uis/Styled/StyledComponents.js +130 -0
  45. package/components/uis/Styled/StyledComponents.tsx +200 -0
  46. package/components/uis/SwitchToggle/SwitchToggle.js +126 -0
  47. package/components/uis/SwitchToggle/SwitchToggle.test.tsx +70 -0
  48. package/components/uis/SwitchToggle/SwitchToggle.tsx +224 -0
  49. package/components/uis/Typography/Typography.js +90 -0
  50. package/components/uis/Typography/Typography.test.tsx +58 -0
  51. package/components/uis/Typography/Typography.tsx +132 -0
  52. package/hooks/useDebouncedColorScheme.js +21 -0
  53. package/hooks/useDebouncedColorScheme.tsx +30 -0
  54. package/index.js +16 -0
  55. package/index.tsx +18 -0
  56. package/package.json +35 -0
  57. package/providers/ThemeProvider.js +106 -0
  58. package/providers/ThemeProvider.tsx +180 -0
  59. package/providers/index.js +62 -0
  60. package/providers/index.tsx +125 -0
  61. package/react-native.config.cjs +5 -0
  62. package/utils/colors.js +124 -0
  63. package/utils/colors.ts +127 -0
  64. package/utils/createCtx.js +15 -0
  65. package/utils/createCtx.tsx +26 -0
  66. package/utils/guards.js +44 -0
  67. package/utils/guards.ts +93 -0
  68. package/utils/theme.js +5 -0
  69. package/utils/theme.ts +33 -0
@@ -0,0 +1,213 @@
1
+ // Caveat: Expo web needs React to be imported
2
+ import React, {useEffect, useRef, useState} from 'react';
3
+ import type {LayoutChangeEvent} from 'react-native';
4
+ import {Animated, Easing, View} from 'react-native';
5
+ import styled, {css} from '@emotion/native';
6
+
7
+ import {Icon} from '../Icon/Icon';
8
+ import {Typography} from '../Typography/Typography';
9
+
10
+ import type {AccordionBaseProps} from './Accordion';
11
+
12
+ const TitleTouch = styled.TouchableOpacity`
13
+ height: 48px;
14
+ background-color: ${({theme}) => theme.bg.basic};
15
+
16
+ flex-direction: row;
17
+ align-items: center;
18
+ padding: 8px 12px;
19
+ `;
20
+
21
+ const StyledIcon = styled(Icon)`
22
+ color: ${({theme}) => theme.text.basic};
23
+ font-weight: bold;
24
+ `;
25
+
26
+ const ItemTouch = styled.TouchableOpacity`
27
+ background-color: ${({theme}) => theme.bg.paper};
28
+ padding: 8px 12px;
29
+
30
+ flex-direction: row;
31
+ align-items: center;
32
+ `;
33
+
34
+ export type AccordionItemDataType<T, K> = {
35
+ title: T;
36
+ items: K[];
37
+ };
38
+
39
+ type Props<T, K> = Omit<AccordionBaseProps<T, K>, 'data' | 'style'> & {
40
+ testID: string;
41
+ data: AccordionItemDataType<string, string> | AccordionItemDataType<T, K>;
42
+ };
43
+
44
+ export function AccordionItem<T, K>({
45
+ testID,
46
+ data: data,
47
+ shouldAnimate = true,
48
+ collapseOnStart = true,
49
+ animDuration = 200,
50
+ activeOpacity = 1,
51
+ toggleElement = <StyledIcon name="CaretDown" size={14} />,
52
+ toggleElementPosition,
53
+ onPressItem,
54
+ renderTitle,
55
+ renderItem,
56
+ styles,
57
+ }: Props<T, K>): JSX.Element {
58
+ const dropDownAnimValueRef = useRef(new Animated.Value(0));
59
+ const rotateAnimValueRef = useRef(new Animated.Value(0));
60
+ const fadeItemAnim = useRef(new Animated.Value(0)).current;
61
+ const [itemHeight, setItemHeight] = useState(0);
62
+ const [collapsed, setCollapsed] = useState(collapseOnStart);
63
+
64
+ useEffect(() => {
65
+ Animated.timing(fadeItemAnim, {
66
+ toValue: collapsed ? 0 : 1,
67
+ duration: !collapsed ? 300 : 100,
68
+ useNativeDriver: false,
69
+ }).start();
70
+ }, [fadeItemAnim, collapsed]);
71
+
72
+ useEffect(() => {
73
+ const targetValue = collapsed ? 0 : 1;
74
+
75
+ if (!shouldAnimate) {
76
+ rotateAnimValueRef.current.setValue(targetValue);
77
+ dropDownAnimValueRef.current.setValue(targetValue);
78
+
79
+ return;
80
+ }
81
+
82
+ const config = {
83
+ duration: animDuration,
84
+ easing: Easing.linear,
85
+ useNativeDriver: false,
86
+ toValue: targetValue,
87
+ };
88
+
89
+ Animated.parallel([
90
+ Animated.timing(rotateAnimValueRef.current, config),
91
+ Animated.timing(dropDownAnimValueRef.current, config),
92
+ ]).start();
93
+ }, [collapsed, shouldAnimate, animDuration]);
94
+
95
+ const toggleElContainer = (
96
+ <Animated.View
97
+ style={[
98
+ css`
99
+ margin-right: ${toggleElementPosition === 'left' ? '12px' : 0};
100
+ `,
101
+ {
102
+ transform: [
103
+ {
104
+ rotate: rotateAnimValueRef.current.interpolate({
105
+ inputRange: [0, 1],
106
+ outputRange: ['0deg', '180deg'],
107
+ }),
108
+ },
109
+ ],
110
+ },
111
+ styles?.toggleElement,
112
+ ]}
113
+ >
114
+ {toggleElement}
115
+ </Animated.View>
116
+ );
117
+
118
+ return (
119
+ <Animated.View
120
+ style={[
121
+ css`
122
+ background-color: transparent;
123
+ overflow: hidden;
124
+ flex-direction: column-reverse;
125
+ `,
126
+ styles?.container,
127
+ ]}
128
+ >
129
+ {/* Invisible: Place it at the top for z-index */}
130
+ <View
131
+ onLayout={(e: LayoutChangeEvent) => {
132
+ setItemHeight(e.nativeEvent.layout.height);
133
+ }}
134
+ style={css`
135
+ position: absolute;
136
+ opacity: 0;
137
+ `}
138
+ >
139
+ {data.items.map((body, index) => (
140
+ <ItemTouch
141
+ activeOpacity={activeOpacity}
142
+ key={`body-${index}`}
143
+ onPress={() => onPressItem?.(data.title, body)}
144
+ >
145
+ {typeof body === 'string' && !renderItem ? (
146
+ <Typography.Body3 style={styles?.itemText}>
147
+ {body}
148
+ </Typography.Body3>
149
+ ) : (
150
+ renderItem?.(body as K)
151
+ )}
152
+ </ItemTouch>
153
+ ))}
154
+ </View>
155
+ {/* Item */}
156
+ <Animated.View
157
+ accessibilityState={{expanded: !collapsed}}
158
+ style={[
159
+ {
160
+ opacity: fadeItemAnim,
161
+ height: dropDownAnimValueRef.current.interpolate({
162
+ inputRange: [0, 1],
163
+ outputRange: [0, itemHeight],
164
+ }),
165
+ },
166
+ ]}
167
+ testID={`body-${testID}`}
168
+ >
169
+ {data.items.map((body, index) => (
170
+ <ItemTouch
171
+ activeOpacity={activeOpacity}
172
+ key={`body-${index}`}
173
+ onPress={() => onPressItem?.(data.title, body)}
174
+ style={styles?.itemContainer}
175
+ >
176
+ {typeof body === 'string' && !renderItem ? (
177
+ <Typography.Body3 style={styles?.itemText}>
178
+ {body}
179
+ </Typography.Body3>
180
+ ) : (
181
+ renderItem?.(body as K)
182
+ )}
183
+ </ItemTouch>
184
+ ))}
185
+ </Animated.View>
186
+
187
+ {/* Title */}
188
+ <TitleTouch
189
+ activeOpacity={activeOpacity}
190
+ onPress={() => setCollapsed(!collapsed)}
191
+ style={[
192
+ css`
193
+ justify-content: ${toggleElementPosition === 'right'
194
+ ? 'space-between'
195
+ : 'flex-start'};
196
+ `,
197
+ styles?.titleContainer,
198
+ ]}
199
+ testID={`title-${testID}`}
200
+ >
201
+ {toggleElementPosition === 'left' ? toggleElContainer : null}
202
+ {typeof data.title === 'string' && !renderTitle ? (
203
+ <Typography.Heading4 style={styles?.titleText}>
204
+ {data.title}
205
+ </Typography.Heading4>
206
+ ) : (
207
+ renderTitle?.(data.title as T)
208
+ )}
209
+ {toggleElementPosition === 'right' ? toggleElContainer : null}
210
+ </TitleTouch>
211
+ </Animated.View>
212
+ );
213
+ }
@@ -0,0 +1,161 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useRef } from 'react';
3
+ import { Platform, Text, TouchableHighlight, View } from 'react-native';
4
+ import { useHover } from 'react-native-web-hooks';
5
+ import styled, { css } from '@emotion/native';
6
+ import { useTheme } from '../../../providers/ThemeProvider';
7
+ import { cloneElemWithDefaultColors } from '../../../utils/guards';
8
+ import { LoadingIndicator } from '../LoadingIndicator/LoadingIndicator';
9
+ const ButtonContainer = styled.View `
10
+ align-self: stretch;
11
+ flex-direction: row;
12
+ align-items: center;
13
+ justify-content: center;
14
+ `;
15
+ const calculateStyles = ({ theme, type, color, size, loading, disabled, borderRadius, hovered, styles, }) => {
16
+ const isDisabled = disabled || loading;
17
+ const padding = type === 'text'
18
+ ? '8px'
19
+ : size === 'large'
20
+ ? '16px 32px'
21
+ : size === 'small'
22
+ ? '8px 16px'
23
+ : '12px 24px';
24
+ return {
25
+ container: [
26
+ css `
27
+ border-radius: ${borderRadius + 'px'};
28
+ padding: ${padding};
29
+ background-color: ${isDisabled
30
+ ? theme.button.disabled.bg
31
+ : ['text', 'outlined'].includes(type)
32
+ ? theme.bg.basic
33
+ : theme.button[color].bg};
34
+ border-color: ${isDisabled
35
+ ? theme.bg.disabled
36
+ : type === 'text'
37
+ ? 'transparent'
38
+ : theme.button[color].bg};
39
+ border-width: ${type !== 'text' ? '1px' : 0};
40
+ `,
41
+ styles?.container,
42
+ ],
43
+ text: [
44
+ css `
45
+ font-family: 'Pretendard';
46
+ color: ${isDisabled
47
+ ? theme.button.disabled.text
48
+ : type === 'solid' || color === 'light'
49
+ ? theme.button[color].text
50
+ : theme.button[color].bg};
51
+ `,
52
+ styles?.text,
53
+ ],
54
+ content: [
55
+ css `
56
+ flex-direction: row;
57
+ align-items: center;
58
+ opacity: ${loading ? '0' : '1'};
59
+ gap: 8px;
60
+ `,
61
+ styles?.content,
62
+ ],
63
+ loading: [
64
+ css `
65
+ position: absolute;
66
+ opacity: ${loading ? '1' : '0'};
67
+ `,
68
+ styles?.loading,
69
+ ],
70
+ hovered: hovered
71
+ ? [
72
+ {
73
+ shadowColor: 'black',
74
+ shadowOpacity: 0.24,
75
+ shadowRadius: 16,
76
+ elevation: 10,
77
+ },
78
+ css `
79
+ border-radius: ${borderRadius + 'px'};
80
+ `,
81
+ styles?.hovered,
82
+ ]
83
+ : undefined,
84
+ disabled: isDisabled
85
+ ? [
86
+ css `
87
+ background-color: ${theme.button.disabled.bg};
88
+ border-color: ${theme.bg.disabled};
89
+ `,
90
+ styles?.disabled,
91
+ ]
92
+ : undefined,
93
+ };
94
+ };
95
+ const useButtonState = ({ disabled, onPress, loading, }) => {
96
+ return {
97
+ innerDisabled: disabled || !onPress,
98
+ isLoading: loading,
99
+ };
100
+ };
101
+ export function Button({ testID, type = 'solid', color = 'primary', size = 'medium', disabled, loading = false, loadingElement, text, startElement, endElement, style, styles, onPress, activeOpacity = 0.8, touchableHighlightProps, borderRadius = 4, loadingColor, hitSlop = { top: 8, bottom: 8, left: 8, right: 8 }, }) {
102
+ const ref = useRef(null);
103
+ const hovered = useHover(ref);
104
+ const { theme } = useTheme();
105
+ const { innerDisabled } = useButtonState({ disabled, onPress, loading });
106
+ const compositeStyles = calculateStyles({
107
+ theme,
108
+ type,
109
+ color,
110
+ size,
111
+ loading,
112
+ disabled: innerDisabled,
113
+ borderRadius,
114
+ hovered,
115
+ styles,
116
+ });
117
+ const LoadingView = loadingElement ?? (_jsx(LoadingIndicator, { color: loadingColor || type === 'solid'
118
+ ? theme.text.contrast
119
+ : theme.text.basic, size: "small" }));
120
+ const renderContainer = useCallback(({ children, loadingView, }) => (_jsxs(ButtonContainer, { disabled: innerDisabled, size: size, style: [
121
+ hovered && !innerDisabled && compositeStyles.hovered,
122
+ compositeStyles.container,
123
+ type === 'text' &&
124
+ css `
125
+ background-color: transparent;
126
+ `,
127
+ innerDisabled && compositeStyles.disabled,
128
+ ], testID: loading ? 'loading-view' : 'button-container', type: type, children: [_jsx(View, { style: compositeStyles.content, children: children }), _jsx(View, { style: compositeStyles.loading, children: loadingView })] })), [
129
+ innerDisabled,
130
+ size,
131
+ compositeStyles.container,
132
+ compositeStyles.hovered,
133
+ compositeStyles.disabled,
134
+ compositeStyles.content,
135
+ compositeStyles.loading,
136
+ hovered,
137
+ type,
138
+ loading,
139
+ ]);
140
+ const ChildView = (_jsxs(_Fragment, { children: [cloneElemWithDefaultColors({
141
+ element: startElement,
142
+ backgroundColor: compositeStyles.container?.[0]?.backgroundColor,
143
+ color: compositeStyles.text?.[0]?.color,
144
+ }), !text || typeof text === 'string' ? (_jsx(Text, { style: compositeStyles.text, children: text })) : (text), cloneElemWithDefaultColors({
145
+ element: endElement,
146
+ backgroundColor: compositeStyles.container?.[0]?.backgroundColor,
147
+ color: compositeStyles.text?.[0]?.color,
148
+ })] }));
149
+ return (_jsx(TouchableHighlight, { activeOpacity: activeOpacity, delayPressIn: 30, disabled: innerDisabled || loading || !onPress, hitSlop: hitSlop, onPress: onPress, ref: Platform.select({
150
+ web: ref,
151
+ default: undefined,
152
+ }), style: [
153
+ style,
154
+ css `
155
+ border-radius: ${borderRadius + 'px'};
156
+ `,
157
+ ], testID: testID, underlayColor: type === 'text' ? 'transparent' : theme.role.underlay, ...touchableHighlightProps, children: renderContainer({
158
+ children: ChildView,
159
+ loadingView: LoadingView,
160
+ }) }));
161
+ }
@@ -0,0 +1,81 @@
1
+ import {action} from '@storybook/addon-actions';
2
+ import type {Meta, StoryObj} from '@storybook/react';
3
+
4
+ import {withThemeProvider} from '../../../../.storybook/decorators';
5
+
6
+ import type {ButtonColorType, ButtonSizeType, ButtonType} from './Button';
7
+ import {Button} from './Button';
8
+
9
+ const buttonTypes: ButtonType[] = ['outlined', 'solid', 'text'];
10
+ const buttonSizes: ButtonSizeType[] = ['large', 'medium', 'small'];
11
+
12
+ const buttonColors: ButtonColorType[] = [
13
+ 'primary',
14
+ 'success',
15
+ 'info',
16
+ 'warning',
17
+ 'danger',
18
+ 'light',
19
+ 'secondary',
20
+ ];
21
+
22
+ const meta = {
23
+ title: 'Button',
24
+ component: Button,
25
+ args: {
26
+ text: 'Hello world',
27
+ type: 'solid',
28
+ color: 'primary',
29
+ size: 'medium',
30
+ onPress: action('onPress'),
31
+ },
32
+ argTypes: {
33
+ type: {
34
+ control: 'select',
35
+ options: buttonTypes,
36
+ },
37
+ color: {
38
+ control: 'select',
39
+ options: buttonColors,
40
+ },
41
+ size: {
42
+ control: 'select',
43
+ options: buttonSizes,
44
+ },
45
+ },
46
+ decorators: [withThemeProvider],
47
+ } satisfies Meta<typeof Button>;
48
+
49
+ export default meta;
50
+
51
+ type Story = StoryObj<typeof meta>;
52
+
53
+ export const Basic: Story = {
54
+ args: {
55
+ text: 'Basic Button',
56
+ type: 'solid',
57
+ color: 'primary',
58
+ size: 'medium',
59
+ onPress: action('onPress'),
60
+ },
61
+ };
62
+
63
+ export const Secondary: Story = {
64
+ args: {
65
+ text: 'Secondary Button',
66
+ type: 'outlined',
67
+ color: 'secondary',
68
+ size: 'large',
69
+ onPress: action('onPress'),
70
+ },
71
+ };
72
+
73
+ export const Danger: Story = {
74
+ args: {
75
+ text: 'Danger Button',
76
+ type: 'solid',
77
+ color: 'danger',
78
+ size: 'small',
79
+ onPress: action('onPress'),
80
+ },
81
+ };