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,174 @@
1
+ import React, {forwardRef, useImperativeHandle, useState} from 'react';
2
+ import type {StyleProp, TextStyle, ViewStyle} from 'react-native';
3
+ import {Modal, Platform, StyleSheet} from 'react-native';
4
+ import styled, {css} from '@emotion/native';
5
+
6
+ import {SnackbarTimer} from './const';
7
+ import {Button, ButtonColorType} from '../../uis/Button/Button';
8
+ import {useTheme} from '../../../providers/ThemeProvider';
9
+ import {Icon} from '../../uis/Icon/Icon';
10
+
11
+ const Container = styled.View`
12
+ flex: 1;
13
+ align-self: stretch;
14
+
15
+ flex-direction: row;
16
+ justify-content: center;
17
+ `;
18
+
19
+ const SnackbarContainer = styled.SafeAreaView<{color: ButtonColorType}>`
20
+ background-color: ${({theme, color}) => theme.button[color].bg};
21
+ border-radius: 8px;
22
+ margin-bottom: 52px;
23
+ margin-left: 12px;
24
+ margin-right: 12px;
25
+ align-self: flex-end;
26
+
27
+ flex-direction: row;
28
+ align-items: center;
29
+ `;
30
+
31
+ const ActionContainer = styled.View`
32
+ margin-right: 4px;
33
+ `;
34
+
35
+ const SnackbarText = styled.Text<{color: ButtonColorType}>`
36
+ font-family: Pretendard;
37
+ color: ${({theme, color}) => theme.button[color].text};
38
+ flex: 1;
39
+ padding: 12px;
40
+ `;
41
+
42
+ export type SnackbarProps = {
43
+ style?: StyleProp<ViewStyle>;
44
+ };
45
+
46
+ export type SnackbarStyles = {
47
+ container?: StyleProp<ViewStyle>;
48
+ text?: StyleProp<TextStyle>;
49
+ actionContainer?: StyleProp<ViewStyle>;
50
+ actionText?: StyleProp<TextStyle>;
51
+ };
52
+
53
+ export type SnackbarOptions = {
54
+ color?: ButtonColorType;
55
+ styles?: SnackbarStyles;
56
+ text?: string;
57
+ actionText?: string;
58
+ timer?: SnackbarTimer | number;
59
+ };
60
+
61
+ export type SnackbarContext = {
62
+ open(snackbarOptions?: SnackbarOptions): void;
63
+ close(): void;
64
+ };
65
+
66
+ let timer: NodeJS.Timeout | null = null;
67
+
68
+ function clearTimer(): void {
69
+ if (timer) {
70
+ clearTimeout(timer);
71
+ timer = null;
72
+ }
73
+ }
74
+
75
+ function Snackbar(
76
+ {style}: SnackbarProps,
77
+ ref: React.Ref<SnackbarContext>,
78
+ ): JSX.Element {
79
+ const [options, setOptions] = useState<SnackbarOptions | null>(null);
80
+ const [visible, setVisible] = useState(false);
81
+ const {theme} = useTheme();
82
+
83
+ useImperativeHandle(ref, () => ({
84
+ open: (snackbarOptions) => {
85
+ clearTimer();
86
+ setVisible(true);
87
+ if (snackbarOptions) {
88
+ setOptions(snackbarOptions);
89
+ }
90
+
91
+ timer = setTimeout(() => {
92
+ setVisible(false);
93
+ clearTimer();
94
+ }, snackbarOptions?.timer ?? SnackbarTimer.SHORT);
95
+ },
96
+ close: () => {
97
+ setVisible(false);
98
+ setOptions(null);
99
+ },
100
+ }));
101
+
102
+ const {text, styles, actionText, color = 'primary'} = options ?? {};
103
+
104
+ const SnackbarContent = (
105
+ <Container>
106
+ <SnackbarContainer
107
+ color={color}
108
+ style={StyleSheet.flatten([
109
+ Platform.OS !== 'web' && {
110
+ shadowOffset: {width: 0, height: 4},
111
+ shadowColor: theme.text.basic,
112
+ },
113
+ styles?.container,
114
+ ])}
115
+ >
116
+ <SnackbarText
117
+ color={color}
118
+ style={StyleSheet.flatten([
119
+ css`
120
+ font-family: 'Pretendard';
121
+ color: ${theme.button[color].text};
122
+ `,
123
+ styles?.text,
124
+ ])}
125
+ >
126
+ {text}
127
+ </SnackbarText>
128
+ <ActionContainer style={styles?.actionContainer}>
129
+ {actionText ? (
130
+ <Button
131
+ onPress={() => setVisible(false)}
132
+ styles={{
133
+ text: StyleSheet.flatten([
134
+ css`
135
+ font-family: 'Pretendard';
136
+ color: ${theme.button[color].text};
137
+ `,
138
+ styles?.actionText,
139
+ ]),
140
+ }}
141
+ text={actionText}
142
+ type="text"
143
+ />
144
+ ) : (
145
+ <Button
146
+ onPress={() => setVisible(false)}
147
+ text={<Icon color={theme.button[color].text} name="X" />}
148
+ type="text"
149
+ />
150
+ )}
151
+ </ActionContainer>
152
+ </SnackbarContainer>
153
+ </Container>
154
+ );
155
+
156
+ return (
157
+ <Modal
158
+ animationType="fade"
159
+ style={[
160
+ css`
161
+ flex: 1;
162
+ align-self: stretch;
163
+ `,
164
+ style,
165
+ ]}
166
+ transparent={true}
167
+ visible={visible}
168
+ >
169
+ {SnackbarContent}
170
+ </Modal>
171
+ );
172
+ }
173
+
174
+ export default forwardRef<SnackbarContext, SnackbarProps>(Snackbar);
@@ -0,0 +1,5 @@
1
+ export var SnackbarTimer;
2
+ (function (SnackbarTimer) {
3
+ SnackbarTimer[SnackbarTimer["SHORT"] = 2000] = "SHORT";
4
+ SnackbarTimer[SnackbarTimer["LONG"] = 3500] = "LONG";
5
+ })(SnackbarTimer || (SnackbarTimer = {}));
@@ -0,0 +1,4 @@
1
+ export enum SnackbarTimer {
2
+ SHORT = 2000,
3
+ LONG = 3500,
4
+ }
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import styled from '@emotion/native';
3
+ import { AccordionItem } from './AccordionItem';
4
+ const Container = styled.View `
5
+ flex-direction: column;
6
+ `;
7
+ export function Accordion({ style, toggleElementPosition = 'right', data, ...rest }) {
8
+ return (_jsx(Container, { style: style, children: data.map((datum, titleKey) => {
9
+ return (_jsx(AccordionItem, { data: datum, testID: `${titleKey}`, toggleElementPosition: toggleElementPosition, ...rest }, titleKey));
10
+ }) }));
11
+ }
@@ -0,0 +1,38 @@
1
+ import type {ComponentProps} from 'react';
2
+ import {css} from '@emotion/native';
3
+ import type {Meta, StoryObj} from '@storybook/react';
4
+ import {Accordion} from './Accordion';
5
+ import {withThemeProvider} from '../../../../.storybook/decorators';
6
+
7
+ const meta = {
8
+ title: 'Accordion',
9
+ component: Accordion,
10
+ decorators: [withThemeProvider],
11
+ } satisfies Meta<typeof Accordion>;
12
+
13
+ export default meta;
14
+
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const Basic: Story = {
18
+ args: {
19
+ animDuration: 200,
20
+ collapseOnStart: true,
21
+ onPressItem: () => {},
22
+ data: [
23
+ {
24
+ title: 'Item 1',
25
+ items: ['User', 'Mail', 'Text'],
26
+ },
27
+ {
28
+ title: 'Item 2',
29
+ items: ['User', 'Mail', 'Text'],
30
+ },
31
+ {
32
+ title: 'Item 3',
33
+ items: ['User', 'Mail', 'Text'],
34
+ },
35
+ ],
36
+ shouldAnimate: true,
37
+ },
38
+ };
@@ -0,0 +1,128 @@
1
+ import React from 'react';
2
+ import {Text} from 'react-native';
3
+ import type {RenderAPI} from '@testing-library/react-native';
4
+ import {fireEvent, render} from '@testing-library/react-native';
5
+
6
+ import {createComponent, createTestProps} from '../../../../test/testUtils';
7
+ import {Accordion} from './Accordion';
8
+
9
+ let props: any;
10
+ let component: JSX.Element;
11
+ let testingLib: RenderAPI;
12
+
13
+ const data: any[] = [
14
+ {
15
+ title: 'title1',
16
+ items: ['body1', 'body2', 'body3'],
17
+ },
18
+ {
19
+ title: 'title2',
20
+ items: ['body1', 'body2', 'body3'],
21
+ },
22
+ {
23
+ title: 'title3',
24
+ items: ['body1', 'body2', 'body3'],
25
+ },
26
+ ];
27
+
28
+ describe('[Accordion] render test', () => {
29
+ it('should render without crashing', () => {
30
+ props = createTestProps({
31
+ data: data,
32
+ renderTitle: (title) => <Text>{title}</Text>,
33
+ renderItem: (item) => <Text>{item}</Text>,
34
+ });
35
+
36
+ component = createComponent(<Accordion {...props} />);
37
+
38
+ testingLib = render(component);
39
+
40
+ const json = testingLib.toJSON();
41
+ expect(json).toBeTruthy();
42
+ });
43
+
44
+ it('should render collapsed when collapseOnStart props is true', () => {
45
+ props = createTestProps({
46
+ collapseOnStart: true,
47
+ data: data,
48
+ renderTitle: (title) => <Text>{title}</Text>,
49
+ renderItem: (item) => <Text>{item}</Text>,
50
+ });
51
+
52
+ component = createComponent(<Accordion {...props} />);
53
+ testingLib = render(component);
54
+
55
+ const json = testingLib.toJSON();
56
+
57
+ expect(json).toBeTruthy();
58
+ });
59
+
60
+ it('should operate animation when shouldAnimate props is true', () => {
61
+ props = createTestProps({
62
+ shouldAnimate: true,
63
+ data: data,
64
+ renderTitle: (title) => <Text>{title}</Text>,
65
+ renderItem: (item) => <Text>{item}</Text>,
66
+ });
67
+
68
+ component = createComponent(<Accordion {...props} />);
69
+
70
+ testingLib = render(component);
71
+
72
+ const json = testingLib.toJSON();
73
+
74
+ expect(json).toBeTruthy();
75
+ });
76
+
77
+ it('should adjust duration of animation depends on animDuration props value', () => {
78
+ props = createTestProps({
79
+ animDuration: 500,
80
+ data: data,
81
+ renderTitle: (title) => <Text>{title}</Text>,
82
+ renderItem: (item) => <Text>{item}</Text>,
83
+ });
84
+
85
+ component = createComponent(<Accordion {...props} />);
86
+ testingLib = render(component);
87
+
88
+ const json = testingLib.toJSON();
89
+
90
+ expect(json).toBeTruthy();
91
+ });
92
+ });
93
+
94
+ describe('[Accordion] event test', () => {
95
+ beforeEach(() => {
96
+ props = createTestProps({
97
+ data: data,
98
+ renderTitle: (title) => <Text>{title}</Text>,
99
+ renderItem: (item) => <Text>{item}</Text>,
100
+ });
101
+
102
+ component = createComponent(<Accordion {...props} />);
103
+ testingLib = render(component);
104
+ });
105
+
106
+ it('should trigger onLayout event when itemBody rendered', () => {
107
+ const {getByTestId} = testingLib;
108
+ const itemTitle = getByTestId('body-0');
109
+
110
+ fireEvent(itemTitle, 'layout', {
111
+ nativeEvent: {
112
+ layout: {
113
+ height: 300,
114
+ },
115
+ },
116
+ });
117
+
118
+ expect(itemTitle.props.style.height).toBeDefined();
119
+ });
120
+
121
+ it('should trigger press event when clicking title', () => {
122
+ fireEvent.press(testingLib.getByTestId('title-0'));
123
+
124
+ expect(
125
+ testingLib.getByTestId('body-0').props.accessibilityState.expanded,
126
+ ).toBeTruthy();
127
+ });
128
+ });
@@ -0,0 +1,58 @@
1
+ import type {StyleProp, TextStyle, ViewStyle} from 'react-native';
2
+ import styled from '@emotion/native';
3
+
4
+ import type {AccordionItemDataType} from './AccordionItem';
5
+ import {AccordionItem} from './AccordionItem';
6
+
7
+ const Container = styled.View`
8
+ flex-direction: column;
9
+ `;
10
+
11
+ type Styles = {
12
+ container?: StyleProp<ViewStyle>;
13
+ titleContainer?: StyleProp<ViewStyle>;
14
+ titleText?: StyleProp<TextStyle>;
15
+ itemContainer?: StyleProp<ViewStyle>;
16
+ itemText?: StyleProp<TextStyle>;
17
+ toggleElement?: StyleProp<ViewStyle>;
18
+ };
19
+
20
+ export type AccordionBaseProps<T = string, K = string> = {
21
+ data: AccordionItemDataType<T, K>[];
22
+ style?: StyleProp<ViewStyle>;
23
+ styles?: Styles;
24
+ shouldAnimate?: boolean;
25
+ collapseOnStart?: boolean;
26
+ animDuration?: number;
27
+ activeOpacity?: number;
28
+ toggleElementPosition?: 'left' | 'right';
29
+ toggleElement?: JSX.Element | null;
30
+ renderTitle?: (title: T) => JSX.Element;
31
+ renderItem?: (body: K) => JSX.Element;
32
+ onPressItem?: (title: T | string, body: K | string) => void;
33
+ };
34
+
35
+ export type AccordionProps<T = string, K = string> = AccordionBaseProps<T, K>;
36
+
37
+ export function Accordion<T, K>({
38
+ style,
39
+ toggleElementPosition = 'right',
40
+ data,
41
+ ...rest
42
+ }: AccordionProps<T, K>): JSX.Element {
43
+ return (
44
+ <Container style={style}>
45
+ {data.map((datum, titleKey) => {
46
+ return (
47
+ <AccordionItem
48
+ data={datum}
49
+ key={titleKey}
50
+ testID={`${titleKey}`}
51
+ toggleElementPosition={toggleElementPosition}
52
+ {...rest}
53
+ />
54
+ );
55
+ })}
56
+ </Container>
57
+ );
58
+ }
@@ -0,0 +1,102 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Caveat: Expo web needs React to be imported
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { Animated, Easing, View } from 'react-native';
5
+ import styled, { css } from '@emotion/native';
6
+ import { Icon } from '../Icon/Icon';
7
+ import { Typography } from '../Typography/Typography';
8
+ const TitleTouch = styled.TouchableOpacity `
9
+ height: 48px;
10
+ background-color: ${({ theme }) => theme.bg.basic};
11
+
12
+ flex-direction: row;
13
+ align-items: center;
14
+ padding: 8px 12px;
15
+ `;
16
+ const StyledIcon = styled(Icon) `
17
+ color: ${({ theme }) => theme.text.basic};
18
+ font-weight: bold;
19
+ `;
20
+ const ItemTouch = styled.TouchableOpacity `
21
+ background-color: ${({ theme }) => theme.bg.paper};
22
+ padding: 8px 12px;
23
+
24
+ flex-direction: row;
25
+ align-items: center;
26
+ `;
27
+ export function AccordionItem({ testID, data: data, shouldAnimate = true, collapseOnStart = true, animDuration = 200, activeOpacity = 1, toggleElement = _jsx(StyledIcon, { name: "CaretDown", size: 14 }), toggleElementPosition, onPressItem, renderTitle, renderItem, styles, }) {
28
+ const dropDownAnimValueRef = useRef(new Animated.Value(0));
29
+ const rotateAnimValueRef = useRef(new Animated.Value(0));
30
+ const fadeItemAnim = useRef(new Animated.Value(0)).current;
31
+ const [itemHeight, setItemHeight] = useState(0);
32
+ const [collapsed, setCollapsed] = useState(collapseOnStart);
33
+ useEffect(() => {
34
+ Animated.timing(fadeItemAnim, {
35
+ toValue: collapsed ? 0 : 1,
36
+ duration: !collapsed ? 300 : 100,
37
+ useNativeDriver: false,
38
+ }).start();
39
+ }, [fadeItemAnim, collapsed]);
40
+ useEffect(() => {
41
+ const targetValue = collapsed ? 0 : 1;
42
+ if (!shouldAnimate) {
43
+ rotateAnimValueRef.current.setValue(targetValue);
44
+ dropDownAnimValueRef.current.setValue(targetValue);
45
+ return;
46
+ }
47
+ const config = {
48
+ duration: animDuration,
49
+ easing: Easing.linear,
50
+ useNativeDriver: false,
51
+ toValue: targetValue,
52
+ };
53
+ Animated.parallel([
54
+ Animated.timing(rotateAnimValueRef.current, config),
55
+ Animated.timing(dropDownAnimValueRef.current, config),
56
+ ]).start();
57
+ }, [collapsed, shouldAnimate, animDuration]);
58
+ const toggleElContainer = (_jsx(Animated.View, { style: [
59
+ css `
60
+ margin-right: ${toggleElementPosition === 'left' ? '12px' : 0};
61
+ `,
62
+ {
63
+ transform: [
64
+ {
65
+ rotate: rotateAnimValueRef.current.interpolate({
66
+ inputRange: [0, 1],
67
+ outputRange: ['0deg', '180deg'],
68
+ }),
69
+ },
70
+ ],
71
+ },
72
+ styles?.toggleElement,
73
+ ], children: toggleElement }));
74
+ return (_jsxs(Animated.View, { style: [
75
+ css `
76
+ background-color: transparent;
77
+ overflow: hidden;
78
+ flex-direction: column-reverse;
79
+ `,
80
+ styles?.container,
81
+ ], children: [_jsx(View, { onLayout: (e) => {
82
+ setItemHeight(e.nativeEvent.layout.height);
83
+ }, style: css `
84
+ position: absolute;
85
+ opacity: 0;
86
+ `, children: data.items.map((body, index) => (_jsx(ItemTouch, { activeOpacity: activeOpacity, onPress: () => onPressItem?.(data.title, body), children: typeof body === 'string' && !renderItem ? (_jsx(Typography.Body3, { style: styles?.itemText, children: body })) : (renderItem?.(body)) }, `body-${index}`))) }), _jsx(Animated.View, { accessibilityState: { expanded: !collapsed }, style: [
87
+ {
88
+ opacity: fadeItemAnim,
89
+ height: dropDownAnimValueRef.current.interpolate({
90
+ inputRange: [0, 1],
91
+ outputRange: [0, itemHeight],
92
+ }),
93
+ },
94
+ ], testID: `body-${testID}`, children: data.items.map((body, index) => (_jsx(ItemTouch, { activeOpacity: activeOpacity, onPress: () => onPressItem?.(data.title, body), style: styles?.itemContainer, children: typeof body === 'string' && !renderItem ? (_jsx(Typography.Body3, { style: styles?.itemText, children: body })) : (renderItem?.(body)) }, `body-${index}`))) }), _jsxs(TitleTouch, { activeOpacity: activeOpacity, onPress: () => setCollapsed(!collapsed), style: [
95
+ css `
96
+ justify-content: ${toggleElementPosition === 'right'
97
+ ? 'space-between'
98
+ : 'flex-start'};
99
+ `,
100
+ styles?.titleContainer,
101
+ ], testID: `title-${testID}`, children: [toggleElementPosition === 'left' ? toggleElContainer : null, typeof data.title === 'string' && !renderTitle ? (_jsx(Typography.Heading4, { style: styles?.titleText, children: data.title })) : (renderTitle?.(data.title)), toggleElementPosition === 'right' ? toggleElContainer : null] })] }));
102
+ }