@coldsurf/ocean-road 1.13.2

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 (195) hide show
  1. package/dist/css/global.css +30 -0
  2. package/dist/index.d.ts +641 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +733 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/native.cjs +94 -0
  7. package/dist/native.cjs.map +1 -0
  8. package/dist/native.d.cts +304 -0
  9. package/dist/native.d.cts.map +1 -0
  10. package/dist/native.d.ts +304 -0
  11. package/dist/native.d.ts.map +1 -0
  12. package/dist/native.js +94 -0
  13. package/dist/native.js.map +1 -0
  14. package/dist/next.cjs +949 -0
  15. package/dist/next.cjs.map +1 -0
  16. package/dist/next.d.cts +270 -0
  17. package/dist/next.d.cts.map +1 -0
  18. package/dist/next.d.ts +270 -0
  19. package/dist/next.d.ts.map +1 -0
  20. package/dist/next.js +949 -0
  21. package/dist/next.js.map +1 -0
  22. package/native/index.d.ts +7 -0
  23. package/next/index.d.ts +7 -0
  24. package/package.json +126 -0
  25. package/src/GlobalStyle.tsx +111 -0
  26. package/src/base/badge/badge.tsx +50 -0
  27. package/src/base/badge/index.ts +1 -0
  28. package/src/base/button/button.styled.tsx +123 -0
  29. package/src/base/button/button.tsx +60 -0
  30. package/src/base/button/button.types.ts +20 -0
  31. package/src/base/button/button.utils.ts +36 -0
  32. package/src/base/button/index.tsx +2 -0
  33. package/src/base/checkbox/checkbox.styled.ts +52 -0
  34. package/src/base/checkbox/checkbox.tsx +26 -0
  35. package/src/base/checkbox/index.ts +1 -0
  36. package/src/base/icon-button/icon-button.styled.ts +8 -0
  37. package/src/base/icon-button/icon-button.tsx +15 -0
  38. package/src/base/icon-button/icon-button.types.ts +3 -0
  39. package/src/base/icon-button/index.ts +2 -0
  40. package/src/base/index.ts +11 -0
  41. package/src/base/label/index.ts +1 -0
  42. package/src/base/label/label.styled.ts +7 -0
  43. package/src/base/label/label.tsx +27 -0
  44. package/src/base/modal/index.ts +1 -0
  45. package/src/base/modal/modal.tsx +59 -0
  46. package/src/base/spinner/index.ts +2 -0
  47. package/src/base/spinner/spinner.styled.ts +25 -0
  48. package/src/base/spinner/spinner.tsx +36 -0
  49. package/src/base/spinner/spinner.types.ts +1 -0
  50. package/src/base/switch/index.ts +1 -0
  51. package/src/base/switch/switch.styled.tsx +49 -0
  52. package/src/base/switch/switch.tsx +29 -0
  53. package/src/base/text/index.ts +1 -0
  54. package/src/base/text/text.styled.ts +17 -0
  55. package/src/base/text/text.tsx +37 -0
  56. package/src/base/text-area/index.ts +2 -0
  57. package/src/base/text-area/text-area.styled.ts +16 -0
  58. package/src/base/text-area/text-area.tsx +29 -0
  59. package/src/base/text-area/text-area.types.ts +11 -0
  60. package/src/base/text-input/index.ts +2 -0
  61. package/src/base/text-input/text-input.styled.ts +40 -0
  62. package/src/base/text-input/text-input.tsx +59 -0
  63. package/src/base/text-input/text-input.types.ts +15 -0
  64. package/src/base/toast/index.ts +2 -0
  65. package/src/base/toast/toast.tsx +60 -0
  66. package/src/base/toast/toast.types.ts +5 -0
  67. package/src/constants.ts +1 -0
  68. package/src/contexts/ColorSchemeProvider.tsx +154 -0
  69. package/src/css/global.css +30 -0
  70. package/src/extensions/accordion/accordion.hooks.ts +11 -0
  71. package/src/extensions/accordion/accordion.tsx +80 -0
  72. package/src/extensions/accordion/index.ts +1 -0
  73. package/src/extensions/app-header/app-header.hooks.ts +94 -0
  74. package/src/extensions/app-header/app-header.tsx +31 -0
  75. package/src/extensions/app-header/app-header.types.ts +1 -0
  76. package/src/extensions/app-header/index.ts +8 -0
  77. package/src/extensions/app-logo/app-logo.tsx +40 -0
  78. package/src/extensions/app-logo/index.ts +1 -0
  79. package/src/extensions/app-store-button/app-store-button.tsx +64 -0
  80. package/src/extensions/app-store-button/index.ts +1 -0
  81. package/src/extensions/brand-icon/brand-icon.android.tsx +11 -0
  82. package/src/extensions/brand-icon/brand-icon.apple.tsx +11 -0
  83. package/src/extensions/brand-icon/brand-icon.google.tsx +11 -0
  84. package/src/extensions/brand-icon/brand-icon.tsx +22 -0
  85. package/src/extensions/brand-icon/index.ts +1 -0
  86. package/src/extensions/color-scheme-toggle/color-scheme-toggle.tsx +76 -0
  87. package/src/extensions/color-scheme-toggle/index.ts +1 -0
  88. package/src/extensions/dropdown/dropdown.menu-item.tsx +237 -0
  89. package/src/extensions/dropdown/dropdown.result-item.tsx +26 -0
  90. package/src/extensions/dropdown/dropdown.styled.tsx +48 -0
  91. package/src/extensions/dropdown/dropdown.trigger.tsx +72 -0
  92. package/src/extensions/dropdown/dropdown.tsx +222 -0
  93. package/src/extensions/dropdown/dropdown.types.ts +3 -0
  94. package/src/extensions/dropdown/dropdown.utils.ts +40 -0
  95. package/src/extensions/dropdown/index.ts +14 -0
  96. package/src/extensions/error-ui/index.ts +7 -0
  97. package/src/extensions/error-ui/network-error/index.ts +1 -0
  98. package/src/extensions/error-ui/network-error/network-error.styled.ts +16 -0
  99. package/src/extensions/error-ui/network-error/network-error.tsx +14 -0
  100. package/src/extensions/error-ui/unknown-error/index.ts +1 -0
  101. package/src/extensions/error-ui/unknown-error/unknown-error.styled.ts +16 -0
  102. package/src/extensions/error-ui/unknown-error/unknown-error.tsx +14 -0
  103. package/src/extensions/full-screen-modal/full-screen-modal.tsx +52 -0
  104. package/src/extensions/full-screen-modal/index.ts +1 -0
  105. package/src/extensions/grid-card-image/grid-card-image.tsx +11 -0
  106. package/src/extensions/grid-card-image/index.ts +1 -0
  107. package/src/extensions/grid-card-image-empty/grid-card-image-empty.tsx +11 -0
  108. package/src/extensions/grid-card-image-empty/index.ts +1 -0
  109. package/src/extensions/grid-card-item/grid-card-item.masonry.styled.tsx +95 -0
  110. package/src/extensions/grid-card-item/grid-card-item.masonry.tsx +63 -0
  111. package/src/extensions/grid-card-item/grid-card-item.styled.tsx +93 -0
  112. package/src/extensions/grid-card-item/grid-card-item.subscribe-btn-layout.tsx +30 -0
  113. package/src/extensions/grid-card-item/grid-card-item.tsx +81 -0
  114. package/src/extensions/grid-card-item/index.ts +2 -0
  115. package/src/extensions/grid-card-list/grid-card-list.masonry.styled.tsx +45 -0
  116. package/src/extensions/grid-card-list/grid-card-list.masonry.tsx +58 -0
  117. package/src/extensions/grid-card-list/grid-card-list.styled.tsx +40 -0
  118. package/src/extensions/grid-card-list/grid-card-list.tsx +59 -0
  119. package/src/extensions/grid-card-list/index.ts +2 -0
  120. package/src/extensions/grid-card-list-empty/grid-card-list-empty.tsx +38 -0
  121. package/src/extensions/grid-card-list-empty/index.ts +1 -0
  122. package/src/extensions/grid-card-list-load-more/grid-card-list-load-more.styled.tsx +15 -0
  123. package/src/extensions/grid-card-list-load-more/grid-card-list-load-more.tsx +43 -0
  124. package/src/extensions/grid-card-list-load-more/index.ts +1 -0
  125. package/src/extensions/index.ts +38 -0
  126. package/src/extensions/menu-item/index.ts +1 -0
  127. package/src/extensions/menu-item/menu-item.tsx +87 -0
  128. package/src/extensions/sns-icon/index.ts +1 -0
  129. package/src/extensions/sns-icon/sns-icon.facebook.tsx +11 -0
  130. package/src/extensions/sns-icon/sns-icon.instagram.tsx +11 -0
  131. package/src/extensions/sns-icon/sns-icon.tsx +24 -0
  132. package/src/extensions/sns-icon/sns-icon.x.tsx +11 -0
  133. package/src/extensions/sns-icon/sns-icon.youtube.tsx +11 -0
  134. package/src/index.ts +8 -0
  135. package/src/native/button/button.styled.tsx +99 -0
  136. package/src/native/button/button.tsx +42 -0
  137. package/src/native/button/index.ts +1 -0
  138. package/src/native/contexts/color-scheme-context/color-scheme-context.tsx +45 -0
  139. package/src/native/contexts/color-scheme-context/index.ts +1 -0
  140. package/src/native/contexts/index.ts +1 -0
  141. package/src/native/icon-button/icon-button.styled.ts +6 -0
  142. package/src/native/icon-button/icon-button.tsx +33 -0
  143. package/src/native/icon-button/icon-button.types.ts +14 -0
  144. package/src/native/icon-button/icon-button.utils.ts +114 -0
  145. package/src/native/icon-button/index.ts +1 -0
  146. package/src/native/index.ts +9 -0
  147. package/src/native/modal/index.ts +2 -0
  148. package/src/native/modal/modal.styled.ts +17 -0
  149. package/src/native/modal/modal.tsx +21 -0
  150. package/src/native/modal/modal.types.ts +8 -0
  151. package/src/native/profile-thumbnail/index.ts +1 -0
  152. package/src/native/profile-thumbnail/profile-thumbnail.tsx +91 -0
  153. package/src/native/spinner/index.ts +1 -0
  154. package/src/native/spinner/spinner.tsx +75 -0
  155. package/src/native/text/index.ts +2 -0
  156. package/src/native/text/text.tsx +51 -0
  157. package/src/native/text/text.types.ts +5 -0
  158. package/src/native/text-input/index.ts +2 -0
  159. package/src/native/text-input/text-input.tsx +72 -0
  160. package/src/native/text-input/text-input.types.ts +3 -0
  161. package/src/native/toast/index.ts +2 -0
  162. package/src/native/toast/toast.styled.ts +40 -0
  163. package/src/native/toast/toast.tsx +23 -0
  164. package/src/native/toast/toast.types.ts +10 -0
  165. package/src/next/app-footer/app-footer.tsx +250 -0
  166. package/src/next/app-footer/index.ts +1 -0
  167. package/src/next/app-header/app-header.fixed-header.tsx +83 -0
  168. package/src/next/app-header/app-header.full-screen-mobile-accordion-drawer.tsx +131 -0
  169. package/src/next/app-header/app-header.logo.tsx +50 -0
  170. package/src/next/app-header/app-header.modal-mobile-accordion-drawer.tsx +69 -0
  171. package/src/next/app-header/app-header.styled.ts +160 -0
  172. package/src/next/app-header/app-header.tsx +91 -0
  173. package/src/next/app-header/index.ts +13 -0
  174. package/src/next/global-link/global-link.store.ts +41 -0
  175. package/src/next/global-link/global-link.tsx +52 -0
  176. package/src/next/global-link/global-link.utils.ts +9 -0
  177. package/src/next/global-link/index.ts +3 -0
  178. package/src/next/grid-card-item/grid-card-item.masonry.tsx +23 -0
  179. package/src/next/grid-card-item/grid-card-item.tsx +23 -0
  180. package/src/next/grid-card-item/index.ts +2 -0
  181. package/src/next/index.ts +16 -0
  182. package/src/next/new-tab-link/index.ts +1 -0
  183. package/src/next/new-tab-link/new-tab-link.tsx +15 -0
  184. package/src/next/route-loading/index.ts +1 -0
  185. package/src/next/route-loading/route-loading.tsx +21 -0
  186. package/src/tokens/index.ts +2 -0
  187. package/src/tokens/tokens.ts +8 -0
  188. package/src/tokens/tokens.types.ts +7 -0
  189. package/src/utils/breakpoints.ts +9 -0
  190. package/src/utils/common-styles.ts +23 -0
  191. package/src/utils/index.ts +2 -0
  192. package/src/utils/media.ts +23 -0
  193. package/src/utils/use-prevent-scroll-effect.ts +19 -0
  194. package/src/utils/with-id.ts +3 -0
  195. package/src/utils/with-stop-propagation.ts +10 -0
@@ -0,0 +1,7 @@
1
+ import styled from '@emotion/styled';
2
+ import { colors } from '../../tokens';
3
+ import { Text } from '../text';
4
+
5
+ export const StyledRequiredLabelMark = styled(Text)`
6
+ color: ${colors.oc.red[7].value};
7
+ `;
@@ -0,0 +1,27 @@
1
+ import { type LabelHTMLAttributes, forwardRef } from 'react';
2
+ import { Text } from '../text';
3
+ import { StyledRequiredLabelMark } from './label.styled';
4
+
5
+ type LabelProps = LabelHTMLAttributes<HTMLLabelElement> & {
6
+ children: string;
7
+ required?: boolean;
8
+ };
9
+
10
+ export const Label = forwardRef<HTMLLabelElement, LabelProps>(
11
+ ({ htmlFor, style, children, required, ...otherProps }, ref) => {
12
+ return (
13
+ <label
14
+ ref={ref}
15
+ {...otherProps}
16
+ htmlFor={htmlFor}
17
+ style={{ marginTop: '1.5rem', marginBottom: '0.5rem', ...style }}
18
+ >
19
+ <Text as="p" style={{ margin: 'unset' }}>
20
+ {children} {required && <StyledRequiredLabelMark as="span">*</StyledRequiredLabelMark>}
21
+ </Text>
22
+ </label>
23
+ );
24
+ }
25
+ );
26
+
27
+ Label.displayName = 'Label';
@@ -0,0 +1 @@
1
+ export * from './modal';
@@ -0,0 +1,59 @@
1
+ import { AnimatePresence, motion } from 'framer-motion';
2
+ import type { PropsWithChildren } from 'react';
3
+ import { semantics } from '../../tokens';
4
+
5
+ export const Modal = ({
6
+ children,
7
+ visible,
8
+ onClose,
9
+ zIndex,
10
+ }: PropsWithChildren<{
11
+ visible: boolean;
12
+ onClose: () => void;
13
+ zIndex?: number;
14
+ }>) => {
15
+ return (
16
+ <AnimatePresence>
17
+ {visible && (
18
+ <>
19
+ <motion.div
20
+ onClick={onClose}
21
+ initial={{ opacity: 0 }}
22
+ animate={{ opacity: 0.25 }}
23
+ exit={{ opacity: 0 }}
24
+ style={{
25
+ background: semantics.color.dimmed[1],
26
+ position: 'fixed',
27
+ left: 0,
28
+ right: 0,
29
+ top: 0,
30
+ bottom: 0,
31
+ zIndex,
32
+ }}
33
+ />
34
+
35
+ <motion.dialog
36
+ open={visible}
37
+ className="modal-content"
38
+ initial={{ opacity: 0 }}
39
+ animate={{ opacity: 1 }}
40
+ exit={{ opacity: 0 }}
41
+ transition={{ duration: 0.1 }}
42
+ style={{
43
+ background: 'transparent',
44
+ position: 'fixed',
45
+ left: 0,
46
+ right: 0,
47
+ top: 0,
48
+ bottom: 0,
49
+ border: 'none',
50
+ zIndex: zIndex ? zIndex + 1 : undefined,
51
+ }}
52
+ >
53
+ {children}
54
+ </motion.dialog>
55
+ </>
56
+ )}
57
+ </AnimatePresence>
58
+ );
59
+ };
@@ -0,0 +1,2 @@
1
+ export * from './spinner';
2
+ export * from './spinner.types';
@@ -0,0 +1,25 @@
1
+ import styled from '@emotion/styled';
2
+ import { motion } from 'framer-motion';
3
+ import { LoaderCircle } from 'lucide-react';
4
+ import { semantics } from '../../tokens';
5
+
6
+ const MotionIcon = motion.create(LoaderCircle);
7
+
8
+ export const StyledPageLoadingWrapper = styled.div`
9
+ position: fixed;
10
+ top: 0;
11
+ left: 0;
12
+ right: 0;
13
+ bottom: 0;
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ background-color: transparent;
18
+ backdrop-filter: blur(1px);
19
+ `;
20
+
21
+ export const StyledSpinner = styled(MotionIcon)`
22
+ color: ${semantics.color.foreground[1]};
23
+ width: 32px;
24
+ height: 32px;
25
+ `;
@@ -0,0 +1,36 @@
1
+ import { StyledPageLoadingWrapper, StyledSpinner } from './spinner.styled';
2
+ import type { SpinnerVariant } from './spinner.types';
3
+
4
+ const RotateSpinner = ({ className }: { className?: string }) => {
5
+ return (
6
+ <StyledSpinner
7
+ animate={{
8
+ rotate: 360, // Rotates the element 360 degrees
9
+ }}
10
+ transition={{
11
+ repeat: Number.POSITIVE_INFINITY, // Loops the animation infinitely
12
+ duration: 0.5, // Each full rotation takes 2 seconds
13
+ ease: 'linear', // Smooth, constant speed
14
+ }}
15
+ className={className}
16
+ />
17
+ );
18
+ };
19
+
20
+ type Props = {
21
+ variant?: SpinnerVariant;
22
+ className?: string;
23
+ };
24
+
25
+ export const Spinner = ({ variant, className }: Props) => {
26
+ switch (variant) {
27
+ case 'page-overlay':
28
+ return (
29
+ <StyledPageLoadingWrapper>
30
+ <RotateSpinner className={className} />
31
+ </StyledPageLoadingWrapper>
32
+ );
33
+ default:
34
+ return <RotateSpinner className={className} />;
35
+ }
36
+ };
@@ -0,0 +1 @@
1
+ export type SpinnerVariant = 'page-overlay';
@@ -0,0 +1 @@
1
+ export * from './switch';
@@ -0,0 +1,49 @@
1
+ import { css } from '@emotion/react';
2
+ import styled from '@emotion/styled';
3
+ import { colors } from '../../tokens';
4
+
5
+ const width = 40;
6
+ const height = width * 0.55;
7
+ const thumbSize = width * 0.45;
8
+
9
+ export const StyledSwitchContainer = styled.button<{ $checked: boolean }>`
10
+ position: relative;
11
+ display: inline-flex;
12
+ align-items: center;
13
+ border-radius: 999px;
14
+ border: none;
15
+ padding: 0;
16
+ cursor: pointer;
17
+ transition: background-color 0.15s ease, opacity 0.15s ease;
18
+ ${({ $checked }) =>
19
+ $checked
20
+ ? css`
21
+ background-color: ${colors.oc.green[5].value};
22
+ `
23
+ : css`
24
+ background-color: ${colors.oc.gray[2].value};
25
+ `}
26
+
27
+ width: ${width}px;
28
+ min-width: ${width}px;
29
+ height: ${height}px;
30
+ `;
31
+
32
+ export const StyledSwitchThumb = styled.span<{ $checked: boolean }>`
33
+ position: absolute;
34
+ left: 2px;
35
+ top: 50%;
36
+ transform: translateY(-50%);
37
+ border-radius: 999px;
38
+ background-color: ${colors.oc.white.value};
39
+ transition: transform 0.15s ease;
40
+
41
+ width: ${thumbSize}px;
42
+ height: ${thumbSize}px;
43
+
44
+ ${({ $checked }) =>
45
+ $checked &&
46
+ css`
47
+ transform: translate(${thumbSize}px, -50%);
48
+ `}
49
+ `;
@@ -0,0 +1,29 @@
1
+ import { type ButtonHTMLAttributes, forwardRef } from 'react';
2
+ import { StyledSwitchContainer, StyledSwitchThumb } from './switch.styled';
3
+
4
+ interface SwitchProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onChange'> {
5
+ checked: boolean;
6
+ onChange: (checked: boolean) => void;
7
+ }
8
+
9
+ export const Switch = forwardRef<HTMLButtonElement, SwitchProps>(
10
+ ({ checked, disabled, onChange, ...buttonProps }, forwardedRef) => {
11
+ return (
12
+ <StyledSwitchContainer
13
+ ref={forwardedRef}
14
+ type="button"
15
+ role="switch"
16
+ aria-checked={checked}
17
+ aria-disabled={disabled}
18
+ disabled={disabled}
19
+ $checked={checked}
20
+ onClick={() => onChange(!checked)}
21
+ {...buttonProps}
22
+ >
23
+ <StyledSwitchThumb $checked={checked} />
24
+ </StyledSwitchContainer>
25
+ );
26
+ }
27
+ );
28
+
29
+ Switch.displayName = 'Switch';
@@ -0,0 +1 @@
1
+ export * from './text';
@@ -0,0 +1,17 @@
1
+ import styled from '@emotion/styled';
2
+
3
+ export const StyledTextContainer = styled.span<{ numberOfLines?: number }>`
4
+ white-space: pre-wrap;
5
+ line-height: 1.25;
6
+
7
+ ${({ numberOfLines }) =>
8
+ numberOfLines &&
9
+ `
10
+ display: -webkit-box;
11
+ overflow: hidden;
12
+ text-overflow: ellipsis;
13
+ -webkit-line-clamp: ${numberOfLines};
14
+ -webkit-box-orient: vertical;
15
+ word-break: break-all;
16
+ `}
17
+ `;
@@ -0,0 +1,37 @@
1
+ import {
2
+ type CSSProperties,
3
+ type ComponentPropsWithRef,
4
+ type ElementType,
5
+ type PropsWithChildren,
6
+ forwardRef,
7
+ } from 'react';
8
+ import { StyledTextContainer } from './text.styled';
9
+
10
+ type TextProps = PropsWithChildren<
11
+ ComponentPropsWithRef<'span'> & {
12
+ style?: CSSProperties;
13
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
14
+ as?: ElementType<any, keyof JSX.IntrinsicElements>;
15
+ numberOfLines?: number;
16
+ }
17
+ >;
18
+
19
+ export const Text = forwardRef<HTMLSpanElement, TextProps>(
20
+ ({ children, style, as, numberOfLines, ...otherProps }, ref) => {
21
+ return (
22
+ <StyledTextContainer
23
+ ref={ref}
24
+ as={as}
25
+ numberOfLines={numberOfLines}
26
+ style={{
27
+ ...style,
28
+ }}
29
+ {...otherProps}
30
+ >
31
+ {children}
32
+ </StyledTextContainer>
33
+ );
34
+ }
35
+ );
36
+
37
+ Text.displayName = 'Text';
@@ -0,0 +1,2 @@
1
+ export * from './text-area';
2
+ export * from './text-area.types';
@@ -0,0 +1,16 @@
1
+ import styled from '@emotion/styled';
2
+ import { colors, semantics } from '../../tokens';
3
+
4
+ export const StyledTextAreaContainer = styled.textarea<{ $isError: boolean }>`
5
+ padding: 1rem;
6
+ background-color: ${semantics.color.background[2]};
7
+ border: 2px solid ${({ $isError }) => ($isError ? colors.oc.red[7].value : semantics.color.border[2])};
8
+ border-radius: 2rem;
9
+ color: ${semantics.color.foreground[1]};
10
+
11
+ &:focus, &:active {
12
+ border: 2px solid ${({ $isError }) => ($isError ? colors.oc.red[7].value : colors.oc.blue[6].value)};
13
+ outline: none;
14
+ box-shadow: none;
15
+ }
16
+ `;
@@ -0,0 +1,29 @@
1
+ import { forwardRef } from 'react';
2
+ import { Label } from '../label';
3
+ import { StyledTextAreaContainer } from './text-area.styled';
4
+ import type { TextAreaProps } from './text-area.types';
5
+
6
+ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
7
+ ({ label, labelStyle, noResize = true, isError, required, ...props }, ref) => {
8
+ return (
9
+ <>
10
+ {label && (
11
+ <Label htmlFor={props.id} style={labelStyle} required={required}>
12
+ {label}
13
+ </Label>
14
+ )}
15
+ <StyledTextAreaContainer
16
+ ref={ref}
17
+ {...props}
18
+ $isError={!!isError}
19
+ style={{
20
+ ...props.style,
21
+ resize: noResize ? 'none' : 'vertical',
22
+ }}
23
+ />
24
+ </>
25
+ );
26
+ }
27
+ );
28
+
29
+ TextArea.displayName = 'TextArea';
@@ -0,0 +1,11 @@
1
+ import type { CSSProperties, DetailedHTMLProps, TextareaHTMLAttributes } from 'react';
2
+
3
+ export type TextAreaProps = DetailedHTMLProps<
4
+ TextareaHTMLAttributes<HTMLTextAreaElement>,
5
+ HTMLTextAreaElement
6
+ > & {
7
+ label?: string;
8
+ labelStyle?: CSSProperties;
9
+ noResize?: boolean;
10
+ isError?: boolean;
11
+ };
@@ -0,0 +1,2 @@
1
+ export * from './text-input';
2
+ export * from './text-input.types';
@@ -0,0 +1,40 @@
1
+ import styled from '@emotion/styled';
2
+ import { colors, semantics } from '../../tokens';
3
+
4
+ export const StyledTextInputContainer = styled.div<{ $isError: boolean }>`
5
+ padding: 1rem;
6
+ background-color: ${semantics.color.background[2]};
7
+ border: 2px solid ${({ $isError }) => ($isError ? colors.oc.red[7].value : semantics.color.border[2])};
8
+ border-radius: 2rem;
9
+ color: ${semantics.color.foreground[1]};
10
+
11
+ display: flex;
12
+ align-items: center;
13
+
14
+ &:focus, &:active {
15
+ border: 2px solid ${({ $isError }) => ($isError ? colors.oc.red[7].value : colors.oc.blue[6].value)};
16
+ outline: none;
17
+ box-shadow: none;
18
+ }
19
+
20
+ &:has(input:focus), &:has(input:active) {
21
+ border: 2px solid ${({ $isError }) => ($isError ? colors.oc.red[7].value : colors.oc.blue[6].value)};
22
+ outline: none;
23
+ box-shadow: none;
24
+ }
25
+ `;
26
+
27
+ export const StyledTextInputInput = styled.input`
28
+ background-color: transparent;
29
+ border: none;
30
+ outline: none;
31
+ width: 100%;
32
+ flex: 1;
33
+ font-family: 'Pretendard';
34
+ padding-top: 0;
35
+ padding-bottom: 0;
36
+ font-size: inherit;
37
+ line-height: 1.25;
38
+
39
+ color: ${semantics.color.foreground[1]};
40
+ `;
@@ -0,0 +1,59 @@
1
+ import {
2
+ type MouseEventHandler,
3
+ forwardRef,
4
+ useCallback,
5
+ useImperativeHandle,
6
+ useRef,
7
+ } from 'react';
8
+ import { Label } from '../label';
9
+ import { StyledTextInputContainer, StyledTextInputInput } from './text-input.styled';
10
+ import type { TextInputProps, TextInputRef } from './text-input.types';
11
+
12
+ export const TextInput = forwardRef<TextInputRef, TextInputProps>(
13
+ (
14
+ { label, labelStyle, isError, required, left, right, className, style, ...inputProps },
15
+ forwardedRef
16
+ ) => {
17
+ const inputRef = useRef<HTMLInputElement>(null);
18
+
19
+ useImperativeHandle(
20
+ forwardedRef,
21
+ () => ({
22
+ focus: () => {
23
+ inputRef.current?.focus();
24
+ },
25
+ blur: () => {
26
+ inputRef.current?.blur();
27
+ },
28
+ }),
29
+ []
30
+ );
31
+
32
+ const onClick = useCallback<MouseEventHandler<HTMLDivElement>>((e) => {
33
+ e.preventDefault();
34
+ inputRef.current?.focus();
35
+ }, []);
36
+
37
+ return (
38
+ <>
39
+ {label && (
40
+ <Label htmlFor={inputProps.id} style={labelStyle} required={required}>
41
+ {label}
42
+ </Label>
43
+ )}
44
+ <StyledTextInputContainer
45
+ className={className}
46
+ $isError={!!isError}
47
+ onClick={onClick}
48
+ style={style}
49
+ >
50
+ {left && left}
51
+ <StyledTextInputInput ref={inputRef} {...inputProps} />
52
+ {right && right}
53
+ </StyledTextInputContainer>
54
+ </>
55
+ );
56
+ }
57
+ );
58
+
59
+ TextInput.displayName = 'TextInput';
@@ -0,0 +1,15 @@
1
+ import type { CSSProperties, InputHTMLAttributes, ReactNode } from 'react';
2
+
3
+ export type TextInputProps = InputHTMLAttributes<HTMLInputElement> & {
4
+ label?: string;
5
+ labelStyle?: CSSProperties;
6
+ isError?: boolean;
7
+ required?: boolean;
8
+ left?: ReactNode;
9
+ right?: ReactNode;
10
+ };
11
+
12
+ export interface TextInputRef {
13
+ focus: () => void;
14
+ blur: () => void;
15
+ }
@@ -0,0 +1,2 @@
1
+ export * from './toast';
2
+ export * from './toast.types';
@@ -0,0 +1,60 @@
1
+ import { motion, usePresence } from 'framer-motion';
2
+ import { useEffect, useRef } from 'react';
3
+ import { semantics } from '../../tokens';
4
+ import { Text } from '../text';
5
+ import type { ToastProps } from './toast.types';
6
+
7
+ const AUTO_CLOSE_TIME = 3000;
8
+
9
+ /**
10
+ * 사용시, framer-motion의 AnimatePresence 컴포넌트를 사용해야 합니다.
11
+ */
12
+ export const Toast = ({ message, zIndex = 99, onClose }: ToastProps) => {
13
+ const [isPresent, safeToRemove] = usePresence();
14
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
15
+ useEffect(() => {
16
+ if (timeoutRef.current) {
17
+ clearTimeout(timeoutRef.current);
18
+ }
19
+ timeoutRef.current = setTimeout(() => {
20
+ onClose();
21
+ }, AUTO_CLOSE_TIME);
22
+
23
+ return () => {
24
+ if (timeoutRef.current) {
25
+ clearTimeout(timeoutRef.current);
26
+ }
27
+ };
28
+ }, [onClose]);
29
+
30
+ useEffect(() => {
31
+ if (!isPresent) {
32
+ safeToRemove();
33
+ }
34
+ }, [isPresent, safeToRemove]);
35
+
36
+ return (
37
+ <motion.div
38
+ initial={{ opacity: 0, y: 50 }}
39
+ animate={{ opacity: 1, y: 0 }}
40
+ exit={{ opacity: 0, y: 50, transition: { duration: 0.3 } }}
41
+ transition={{ duration: 0.3 }}
42
+ onClick={onClose}
43
+ style={{
44
+ background: semantics.color.background[1],
45
+ padding: '16px 16px',
46
+ margin: '0 16px',
47
+ borderRadius: '8px',
48
+ position: 'fixed',
49
+ bottom: '45px',
50
+ left: 'calc(50% - 192.5px)',
51
+ width: 'calc(385px - 32px - 32px)',
52
+ zIndex,
53
+ boxShadow: `0px 0px 12px 0px ${semantics.color.dimmed[1]}`,
54
+ cursor: 'pointer',
55
+ }}
56
+ >
57
+ <Text as="p">{message}</Text>
58
+ </motion.div>
59
+ );
60
+ };
@@ -0,0 +1,5 @@
1
+ export type ToastProps = {
2
+ message: string;
3
+ zIndex?: number;
4
+ onClose: () => void;
5
+ };
@@ -0,0 +1 @@
1
+ export const WEB_APP_HEADER_HEIGHT = '100px';