@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.
- package/dist/css/global.css +30 -0
- package/dist/index.d.ts +641 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +733 -0
- package/dist/index.js.map +1 -0
- package/dist/native.cjs +94 -0
- package/dist/native.cjs.map +1 -0
- package/dist/native.d.cts +304 -0
- package/dist/native.d.cts.map +1 -0
- package/dist/native.d.ts +304 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +94 -0
- package/dist/native.js.map +1 -0
- package/dist/next.cjs +949 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +270 -0
- package/dist/next.d.cts.map +1 -0
- package/dist/next.d.ts +270 -0
- package/dist/next.d.ts.map +1 -0
- package/dist/next.js +949 -0
- package/dist/next.js.map +1 -0
- package/native/index.d.ts +7 -0
- package/next/index.d.ts +7 -0
- package/package.json +126 -0
- package/src/GlobalStyle.tsx +111 -0
- package/src/base/badge/badge.tsx +50 -0
- package/src/base/badge/index.ts +1 -0
- package/src/base/button/button.styled.tsx +123 -0
- package/src/base/button/button.tsx +60 -0
- package/src/base/button/button.types.ts +20 -0
- package/src/base/button/button.utils.ts +36 -0
- package/src/base/button/index.tsx +2 -0
- package/src/base/checkbox/checkbox.styled.ts +52 -0
- package/src/base/checkbox/checkbox.tsx +26 -0
- package/src/base/checkbox/index.ts +1 -0
- package/src/base/icon-button/icon-button.styled.ts +8 -0
- package/src/base/icon-button/icon-button.tsx +15 -0
- package/src/base/icon-button/icon-button.types.ts +3 -0
- package/src/base/icon-button/index.ts +2 -0
- package/src/base/index.ts +11 -0
- package/src/base/label/index.ts +1 -0
- package/src/base/label/label.styled.ts +7 -0
- package/src/base/label/label.tsx +27 -0
- package/src/base/modal/index.ts +1 -0
- package/src/base/modal/modal.tsx +59 -0
- package/src/base/spinner/index.ts +2 -0
- package/src/base/spinner/spinner.styled.ts +25 -0
- package/src/base/spinner/spinner.tsx +36 -0
- package/src/base/spinner/spinner.types.ts +1 -0
- package/src/base/switch/index.ts +1 -0
- package/src/base/switch/switch.styled.tsx +49 -0
- package/src/base/switch/switch.tsx +29 -0
- package/src/base/text/index.ts +1 -0
- package/src/base/text/text.styled.ts +17 -0
- package/src/base/text/text.tsx +37 -0
- package/src/base/text-area/index.ts +2 -0
- package/src/base/text-area/text-area.styled.ts +16 -0
- package/src/base/text-area/text-area.tsx +29 -0
- package/src/base/text-area/text-area.types.ts +11 -0
- package/src/base/text-input/index.ts +2 -0
- package/src/base/text-input/text-input.styled.ts +40 -0
- package/src/base/text-input/text-input.tsx +59 -0
- package/src/base/text-input/text-input.types.ts +15 -0
- package/src/base/toast/index.ts +2 -0
- package/src/base/toast/toast.tsx +60 -0
- package/src/base/toast/toast.types.ts +5 -0
- package/src/constants.ts +1 -0
- package/src/contexts/ColorSchemeProvider.tsx +154 -0
- package/src/css/global.css +30 -0
- package/src/extensions/accordion/accordion.hooks.ts +11 -0
- package/src/extensions/accordion/accordion.tsx +80 -0
- package/src/extensions/accordion/index.ts +1 -0
- package/src/extensions/app-header/app-header.hooks.ts +94 -0
- package/src/extensions/app-header/app-header.tsx +31 -0
- package/src/extensions/app-header/app-header.types.ts +1 -0
- package/src/extensions/app-header/index.ts +8 -0
- package/src/extensions/app-logo/app-logo.tsx +40 -0
- package/src/extensions/app-logo/index.ts +1 -0
- package/src/extensions/app-store-button/app-store-button.tsx +64 -0
- package/src/extensions/app-store-button/index.ts +1 -0
- package/src/extensions/brand-icon/brand-icon.android.tsx +11 -0
- package/src/extensions/brand-icon/brand-icon.apple.tsx +11 -0
- package/src/extensions/brand-icon/brand-icon.google.tsx +11 -0
- package/src/extensions/brand-icon/brand-icon.tsx +22 -0
- package/src/extensions/brand-icon/index.ts +1 -0
- package/src/extensions/color-scheme-toggle/color-scheme-toggle.tsx +76 -0
- package/src/extensions/color-scheme-toggle/index.ts +1 -0
- package/src/extensions/dropdown/dropdown.menu-item.tsx +237 -0
- package/src/extensions/dropdown/dropdown.result-item.tsx +26 -0
- package/src/extensions/dropdown/dropdown.styled.tsx +48 -0
- package/src/extensions/dropdown/dropdown.trigger.tsx +72 -0
- package/src/extensions/dropdown/dropdown.tsx +222 -0
- package/src/extensions/dropdown/dropdown.types.ts +3 -0
- package/src/extensions/dropdown/dropdown.utils.ts +40 -0
- package/src/extensions/dropdown/index.ts +14 -0
- package/src/extensions/error-ui/index.ts +7 -0
- package/src/extensions/error-ui/network-error/index.ts +1 -0
- package/src/extensions/error-ui/network-error/network-error.styled.ts +16 -0
- package/src/extensions/error-ui/network-error/network-error.tsx +14 -0
- package/src/extensions/error-ui/unknown-error/index.ts +1 -0
- package/src/extensions/error-ui/unknown-error/unknown-error.styled.ts +16 -0
- package/src/extensions/error-ui/unknown-error/unknown-error.tsx +14 -0
- package/src/extensions/full-screen-modal/full-screen-modal.tsx +52 -0
- package/src/extensions/full-screen-modal/index.ts +1 -0
- package/src/extensions/grid-card-image/grid-card-image.tsx +11 -0
- package/src/extensions/grid-card-image/index.ts +1 -0
- package/src/extensions/grid-card-image-empty/grid-card-image-empty.tsx +11 -0
- package/src/extensions/grid-card-image-empty/index.ts +1 -0
- package/src/extensions/grid-card-item/grid-card-item.masonry.styled.tsx +95 -0
- package/src/extensions/grid-card-item/grid-card-item.masonry.tsx +63 -0
- package/src/extensions/grid-card-item/grid-card-item.styled.tsx +93 -0
- package/src/extensions/grid-card-item/grid-card-item.subscribe-btn-layout.tsx +30 -0
- package/src/extensions/grid-card-item/grid-card-item.tsx +81 -0
- package/src/extensions/grid-card-item/index.ts +2 -0
- package/src/extensions/grid-card-list/grid-card-list.masonry.styled.tsx +45 -0
- package/src/extensions/grid-card-list/grid-card-list.masonry.tsx +58 -0
- package/src/extensions/grid-card-list/grid-card-list.styled.tsx +40 -0
- package/src/extensions/grid-card-list/grid-card-list.tsx +59 -0
- package/src/extensions/grid-card-list/index.ts +2 -0
- package/src/extensions/grid-card-list-empty/grid-card-list-empty.tsx +38 -0
- package/src/extensions/grid-card-list-empty/index.ts +1 -0
- package/src/extensions/grid-card-list-load-more/grid-card-list-load-more.styled.tsx +15 -0
- package/src/extensions/grid-card-list-load-more/grid-card-list-load-more.tsx +43 -0
- package/src/extensions/grid-card-list-load-more/index.ts +1 -0
- package/src/extensions/index.ts +38 -0
- package/src/extensions/menu-item/index.ts +1 -0
- package/src/extensions/menu-item/menu-item.tsx +87 -0
- package/src/extensions/sns-icon/index.ts +1 -0
- package/src/extensions/sns-icon/sns-icon.facebook.tsx +11 -0
- package/src/extensions/sns-icon/sns-icon.instagram.tsx +11 -0
- package/src/extensions/sns-icon/sns-icon.tsx +24 -0
- package/src/extensions/sns-icon/sns-icon.x.tsx +11 -0
- package/src/extensions/sns-icon/sns-icon.youtube.tsx +11 -0
- package/src/index.ts +8 -0
- package/src/native/button/button.styled.tsx +99 -0
- package/src/native/button/button.tsx +42 -0
- package/src/native/button/index.ts +1 -0
- package/src/native/contexts/color-scheme-context/color-scheme-context.tsx +45 -0
- package/src/native/contexts/color-scheme-context/index.ts +1 -0
- package/src/native/contexts/index.ts +1 -0
- package/src/native/icon-button/icon-button.styled.ts +6 -0
- package/src/native/icon-button/icon-button.tsx +33 -0
- package/src/native/icon-button/icon-button.types.ts +14 -0
- package/src/native/icon-button/icon-button.utils.ts +114 -0
- package/src/native/icon-button/index.ts +1 -0
- package/src/native/index.ts +9 -0
- package/src/native/modal/index.ts +2 -0
- package/src/native/modal/modal.styled.ts +17 -0
- package/src/native/modal/modal.tsx +21 -0
- package/src/native/modal/modal.types.ts +8 -0
- package/src/native/profile-thumbnail/index.ts +1 -0
- package/src/native/profile-thumbnail/profile-thumbnail.tsx +91 -0
- package/src/native/spinner/index.ts +1 -0
- package/src/native/spinner/spinner.tsx +75 -0
- package/src/native/text/index.ts +2 -0
- package/src/native/text/text.tsx +51 -0
- package/src/native/text/text.types.ts +5 -0
- package/src/native/text-input/index.ts +2 -0
- package/src/native/text-input/text-input.tsx +72 -0
- package/src/native/text-input/text-input.types.ts +3 -0
- package/src/native/toast/index.ts +2 -0
- package/src/native/toast/toast.styled.ts +40 -0
- package/src/native/toast/toast.tsx +23 -0
- package/src/native/toast/toast.types.ts +10 -0
- package/src/next/app-footer/app-footer.tsx +250 -0
- package/src/next/app-footer/index.ts +1 -0
- package/src/next/app-header/app-header.fixed-header.tsx +83 -0
- package/src/next/app-header/app-header.full-screen-mobile-accordion-drawer.tsx +131 -0
- package/src/next/app-header/app-header.logo.tsx +50 -0
- package/src/next/app-header/app-header.modal-mobile-accordion-drawer.tsx +69 -0
- package/src/next/app-header/app-header.styled.ts +160 -0
- package/src/next/app-header/app-header.tsx +91 -0
- package/src/next/app-header/index.ts +13 -0
- package/src/next/global-link/global-link.store.ts +41 -0
- package/src/next/global-link/global-link.tsx +52 -0
- package/src/next/global-link/global-link.utils.ts +9 -0
- package/src/next/global-link/index.ts +3 -0
- package/src/next/grid-card-item/grid-card-item.masonry.tsx +23 -0
- package/src/next/grid-card-item/grid-card-item.tsx +23 -0
- package/src/next/grid-card-item/index.ts +2 -0
- package/src/next/index.ts +16 -0
- package/src/next/new-tab-link/index.ts +1 -0
- package/src/next/new-tab-link/new-tab-link.tsx +15 -0
- package/src/next/route-loading/index.ts +1 -0
- package/src/next/route-loading/route-loading.tsx +21 -0
- package/src/tokens/index.ts +2 -0
- package/src/tokens/tokens.ts +8 -0
- package/src/tokens/tokens.types.ts +7 -0
- package/src/utils/breakpoints.ts +9 -0
- package/src/utils/common-styles.ts +23 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/media.ts +23 -0
- package/src/utils/use-prevent-scroll-effect.ts +19 -0
- package/src/utils/with-id.ts +3 -0
- package/src/utils/with-stop-propagation.ts +10 -0
|
@@ -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,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,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,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,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
|
+
};
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const WEB_APP_HEADER_HEIGHT = '100px';
|