@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,250 @@
1
+ 'use client';
2
+
3
+ import { Text } from '@/base';
4
+ import { AppLogo, AppStoreButton, SNSIcon } from '@/extensions';
5
+ import { semantics } from '@/tokens';
6
+ import { media } from '@/utils';
7
+ import { css } from '@emotion/react';
8
+ import styled from '@emotion/styled';
9
+ import Link from 'next/link';
10
+ import { GlobalLink } from '../global-link';
11
+ import { NewTabLink } from '../new-tab-link';
12
+
13
+ const FooterContainer = styled.footer`
14
+ display: flex;
15
+ flex-direction: column;
16
+ margin-top: 15rem;
17
+ `;
18
+
19
+ const FooterInnerContainer = styled.div`
20
+ margin: 0 auto;
21
+ max-width: 100%;
22
+ min-width: 100%;
23
+
24
+ ${media['xx-large'](css`
25
+ margin-left: 1rem;
26
+ margin-right: 1rem;
27
+ min-width: unset;
28
+ max-width: 100%;
29
+ `)}
30
+ `;
31
+
32
+ const FooterTopContainer = styled(FooterInnerContainer)`
33
+ display: flex;
34
+ align-items: flex-start;
35
+ padding-bottom: 1.5rem;
36
+
37
+ ${media.medium(css`
38
+ flex-direction: column;
39
+ `)}
40
+ `;
41
+
42
+ const FooterMiddleContainer = styled(FooterInnerContainer)`
43
+ display: flex;
44
+ flex-direction: row;
45
+ gap: 0.5rem;
46
+ `;
47
+
48
+ const FooterBottomContainer = styled(FooterInnerContainer)`
49
+ display: flex;
50
+ flex-direction: row;
51
+ align-items: center;
52
+ gap: 1rem;
53
+
54
+ padding-top: 1.5rem;
55
+ padding-bottom: 1.5rem;
56
+
57
+ border-top: 1px solid ${semantics.color.border[2]};
58
+
59
+ ${media.medium(css`
60
+ flex-direction: column;
61
+ align-items: flex-start;
62
+ `)}
63
+ `;
64
+
65
+ const FooterText = styled(Text)`
66
+ margin: unset;
67
+ color: ${semantics.color.foreground[1]};
68
+ `;
69
+
70
+ const StyledLinksContainer = styled.div`
71
+ display: flex;
72
+ flex-direction: row;
73
+ gap: 1rem;
74
+ margin-left: auto;
75
+
76
+ ${media.medium(css`
77
+ flex-direction: column;
78
+ margin-left: unset;
79
+ margin-top: 1.5rem;
80
+ `)}
81
+ `;
82
+
83
+ const StyledSNSLinksContainer = styled.div`
84
+ display: flex;
85
+ flex-direction: row;
86
+ gap: 1rem;
87
+ margin-left: 2.5rem;
88
+
89
+ ${media.medium(css`
90
+ margin-left: unset;
91
+ margin-top: 1.5rem;
92
+ `)}
93
+ `;
94
+
95
+ const StyledSNSIcon = styled(SNSIcon)`
96
+ width: 1.5rem;
97
+ height: 1.5rem;
98
+ color: ${semantics.color.foreground[1]};
99
+ fill: ${semantics.color.foreground[1]};
100
+ cursor: pointer;
101
+
102
+ ${media.medium(css`
103
+ width: 1.25rem;
104
+ height: 1.25rem;
105
+ `)}
106
+ `;
107
+
108
+ const StyledSNSLogo = ({ social, url }: { social: 'instagram' | 'x'; url: string }) => {
109
+ return (
110
+ <NewTabLink href={url}>
111
+ <StyledSNSIcon social={social} />
112
+ </NewTabLink>
113
+ );
114
+ };
115
+
116
+ const StyledAppLogo = styled(AppLogo)`
117
+ width: 5rem;
118
+ height: 5rem;
119
+
120
+ ${media.medium(css`
121
+ margin-left: -1rem;
122
+ `)}
123
+ `;
124
+
125
+ const StyledTopFooterMenuContainer = styled.div`
126
+ margin-left: 8rem;
127
+
128
+ ${media.medium(css`
129
+ margin-left: unset;
130
+ padding-right: unset;
131
+ margin-top: 2rem;
132
+ `)}
133
+ `;
134
+
135
+ const StyledTopFooterMenuText = styled(Text)`
136
+ font-size: 1.05rem;
137
+ font-weight: 600;
138
+ color: ${semantics.color.foreground[1]};
139
+ margin: unset;
140
+ flex: 1;
141
+ `;
142
+
143
+ const StyledTopFooterSubMenuText = styled(Text)`
144
+ font-size: 0.95rem;
145
+ font-weight: 500;
146
+ color: ${semantics.color.foreground[3]};
147
+ margin: unset;
148
+ flex: 1;
149
+ margin-top: 0.5rem;
150
+ `;
151
+
152
+ const footerMenuItems = [
153
+ {
154
+ sectionTitle: 'About',
155
+ items: [
156
+ {
157
+ title: 'Mission',
158
+ href: 'https://coldsurf.io/about',
159
+ },
160
+ {
161
+ title: 'Blog',
162
+ href: 'https://blog.coldsurf.io',
163
+ },
164
+ ],
165
+ },
166
+ {
167
+ sectionTitle: 'Support',
168
+ items: [
169
+ {
170
+ title: 'Buy me a coffee',
171
+ href: 'https://ko-fi.com/coldsurf',
172
+ },
173
+ ],
174
+ },
175
+ {
176
+ sectionTitle: 'Work with us',
177
+ items: [
178
+ {
179
+ title: 'Partners',
180
+ href: 'https://coldsurf.io/partners',
181
+ },
182
+ {
183
+ title: 'Makers',
184
+ href: 'https://coldsurf.io/makers',
185
+ },
186
+ ],
187
+ },
188
+ ] as const;
189
+
190
+ export function AppFooter({
191
+ appStoreUrl,
192
+ playStoreUrl,
193
+ instagramUrl,
194
+ xUrl,
195
+ }: {
196
+ appStoreUrl: string;
197
+ playStoreUrl: string;
198
+ instagramUrl: string;
199
+ xUrl: string;
200
+ }) {
201
+ return (
202
+ <FooterContainer>
203
+ <FooterTopContainer>
204
+ <GlobalLink href={'https://coldsurf.io'}>
205
+ <StyledAppLogo type="round" logoTheme="white-background" />
206
+ </GlobalLink>
207
+ <div style={{ flex: 1 }} />
208
+ {footerMenuItems.map((item) => {
209
+ return (
210
+ <StyledTopFooterMenuContainer key={item.sectionTitle}>
211
+ <StyledTopFooterMenuText as="p">{item.sectionTitle}</StyledTopFooterMenuText>
212
+ {item.items.map((item) => {
213
+ return (
214
+ <Link key={item.href} href={item.href}>
215
+ <StyledTopFooterSubMenuText as="p">{item.title}</StyledTopFooterSubMenuText>
216
+ </Link>
217
+ );
218
+ })}
219
+ </StyledTopFooterMenuContainer>
220
+ );
221
+ })}
222
+ </FooterTopContainer>
223
+ <FooterMiddleContainer>
224
+ <NewTabLink href={appStoreUrl}>
225
+ <AppStoreButton store="app-store" />
226
+ </NewTabLink>
227
+ <NewTabLink href={playStoreUrl}>
228
+ <AppStoreButton store="google-play" />
229
+ </NewTabLink>
230
+ </FooterMiddleContainer>
231
+ <FooterBottomContainer>
232
+ <Text as="p" style={{ fontWeight: 'bold', margin: 'unset' }}>
233
+ &copy; 2026 COLDSURF, Inc.
234
+ </Text>
235
+ <StyledLinksContainer>
236
+ <GlobalLink href="https://coldsurf.io/privacy-policy">
237
+ <FooterText as="p">개인정보 처리방침</FooterText>
238
+ </GlobalLink>
239
+ <GlobalLink href="https://coldsurf.io/terms-of-service">
240
+ <FooterText as="p">이용약관</FooterText>
241
+ </GlobalLink>
242
+ </StyledLinksContainer>
243
+ <StyledSNSLinksContainer>
244
+ <StyledSNSLogo social="instagram" url={instagramUrl} />
245
+ <StyledSNSLogo social="x" url={xUrl} />
246
+ </StyledSNSLinksContainer>
247
+ </FooterBottomContainer>
248
+ </FooterContainer>
249
+ );
250
+ }
@@ -0,0 +1 @@
1
+ export * from './app-footer';
@@ -0,0 +1,83 @@
1
+ 'use client';
2
+
3
+ import { IconButton } from '@/base';
4
+ import { AppHeader } from '@/extensions';
5
+ import { semantics } from '@/tokens';
6
+ import { media } from '@/utils';
7
+ import { commonHorizontalLayoutCss } from '@/utils/common-styles';
8
+ import { css } from '@emotion/react';
9
+ import styled from '@emotion/styled';
10
+ import { AlignRight } from 'lucide-react';
11
+ import { type ReactNode, memo } from 'react';
12
+ import { AppHeaderLogo } from './app-header.logo';
13
+
14
+ const HeaderContainer = styled(AppHeader.AnimatedHeader)<{ $headerHeight?: string }>`
15
+ display: flex;
16
+ align-items: center;
17
+ padding: 0 40px;
18
+
19
+ background-color: ${semantics.color.background[2]};
20
+
21
+ height: ${(props) => props.$headerHeight ?? '100px'};
22
+
23
+ ${commonHorizontalLayoutCss(['left', 'right'])}
24
+ `;
25
+
26
+ const MobileMenuContainer = styled.div`
27
+ display: none;
28
+
29
+ ${media['x-large'](css`
30
+ display: flex;
31
+ `)}
32
+ `;
33
+
34
+ const MobileMenuIcon = styled(AlignRight)`
35
+ color: ${semantics.color.foreground[3]};
36
+ `;
37
+
38
+ const MobileMenuOpener = ({
39
+ onClick,
40
+ leftAccessory,
41
+ }: {
42
+ onClick?: () => void;
43
+ leftAccessory?: ReactNode;
44
+ }) => {
45
+ return (
46
+ <MobileMenuContainer>
47
+ {leftAccessory}
48
+ <IconButton onClick={onClick}>
49
+ <MobileMenuIcon />
50
+ </IconButton>
51
+ </MobileMenuContainer>
52
+ );
53
+ };
54
+
55
+ export const FixedHeader = memo(
56
+ ({
57
+ zIndex,
58
+ onClickOpenDrawer,
59
+ mobileLeftAccessory,
60
+ HeaderMenuItemComponent,
61
+ logoRightAccessory,
62
+ headerHeight,
63
+ }: {
64
+ zIndex?: number;
65
+ onClickOpenDrawer?: () => void;
66
+ mobileLeftAccessory?: ReactNode;
67
+ HeaderMenuItemComponent: ReactNode;
68
+ logoRightAccessory?: ReactNode;
69
+ headerHeight?: string;
70
+ }) => {
71
+ const { headerAnimation } = AppHeader.useHeaderScrollAnimation();
72
+
73
+ return (
74
+ <HeaderContainer animation={headerAnimation} zIndex={zIndex} $headerHeight={headerHeight}>
75
+ <AppHeaderLogo logoRightAccessory={logoRightAccessory} />
76
+ {HeaderMenuItemComponent}
77
+ <MobileMenuOpener leftAccessory={mobileLeftAccessory} onClick={onClickOpenDrawer} />
78
+ </HeaderContainer>
79
+ );
80
+ }
81
+ );
82
+
83
+ FixedHeader.displayName = 'AppHeader.FixedHeader';
@@ -0,0 +1,131 @@
1
+ 'use client';
2
+
3
+ import { Accordion, type AccordionProps, FullScreenModal } from '@/extensions';
4
+ import { useIsMobileMenuOpen } from '@/extensions/app-header/app-header.hooks';
5
+ import { usePreventScrollEffect } from '@/utils/use-prevent-scroll-effect';
6
+ import { AnimatePresence, type Variants } from 'framer-motion';
7
+ import { type ReactNode, useCallback } from 'react';
8
+ import {
9
+ StyledFloatingHeaderCloseDrawerButton,
10
+ StyledFloatingHeaderCloseDrawerIcon,
11
+ StyledFullScreenMobileMenuBackground,
12
+ } from './app-header.styled';
13
+
14
+ type CommonProps<ItemT> = {
15
+ onClickClose?: (params: { isOpen: boolean }) => void;
16
+ ColorSchemeToggleComponent: ReactNode;
17
+ } & AccordionProps<ItemT>;
18
+
19
+ type FullScreenMobileMenuProps<ItemT> =
20
+ | ({
21
+ standalone: true;
22
+ isOpen: boolean;
23
+ zIndex?: number;
24
+ } & CommonProps<ItemT>)
25
+ | ({
26
+ standalone: false;
27
+ } & CommonProps<ItemT>);
28
+
29
+ // @TODO: too many anti patterns. need to refactor
30
+ const MobileMenuContent = <ItemT extends { accordionKey: string }>(
31
+ props: FullScreenMobileMenuProps<ItemT>
32
+ ) => {
33
+ // Animation variants
34
+ const menuVariants: Variants = {
35
+ hidden: {
36
+ x: '100%', // Start off-screen to the right
37
+ },
38
+ visible: {
39
+ x: 0, // Slide into view
40
+ transition: {
41
+ type: 'tween',
42
+ duration: 0.25,
43
+ stiffness: 60,
44
+ damping: 12,
45
+ },
46
+ },
47
+ exit: {
48
+ x: '100%', // Slide out to the right
49
+ transition: {
50
+ type: 'tween',
51
+ duration: 0.25,
52
+ stiffness: 60,
53
+ damping: 12,
54
+ },
55
+ },
56
+ };
57
+
58
+ const mobileMenuStore = useIsMobileMenuOpen();
59
+
60
+ const isOpen = props.standalone ? props.isOpen : mobileMenuStore.isMobileMenuOpen;
61
+
62
+ const handleClickClose = useCallback(() => {
63
+ props.onClickClose?.({
64
+ isOpen,
65
+ });
66
+ if (!props.standalone) {
67
+ if (isOpen) {
68
+ mobileMenuStore.closeMobileMenu();
69
+ } else {
70
+ mobileMenuStore.openMobileMenu();
71
+ }
72
+ }
73
+ }, [isOpen, mobileMenuStore, props]);
74
+
75
+ usePreventScrollEffect({
76
+ shouldPrevent: isOpen,
77
+ });
78
+
79
+ return (
80
+ <AnimatePresence>
81
+ {isOpen && (
82
+ <StyledFullScreenMobileMenuBackground
83
+ initial="hidden"
84
+ animate="visible"
85
+ exit="exit"
86
+ variants={menuVariants}
87
+ $standalone={props.standalone}
88
+ >
89
+ {props.standalone && (
90
+ <StyledFloatingHeaderCloseDrawerButton $isOpen={isOpen} onClick={handleClickClose}>
91
+ <StyledFloatingHeaderCloseDrawerIcon />
92
+ </StyledFloatingHeaderCloseDrawerButton>
93
+ )}
94
+ <ul style={{ listStyleType: 'none', padding: 0 }}>
95
+ <Accordion
96
+ data={props.data}
97
+ renderTrigger={props.renderTrigger}
98
+ renderExpanded={props.renderExpanded}
99
+ customized={props.customized}
100
+ />
101
+ </ul>
102
+ {props.ColorSchemeToggleComponent}
103
+ </StyledFullScreenMobileMenuBackground>
104
+ )}
105
+ </AnimatePresence>
106
+ );
107
+ };
108
+
109
+ export const FullScreenMobileAccordionDrawer = <ItemT extends { accordionKey: string }>(
110
+ props: FullScreenMobileMenuProps<ItemT>
111
+ ) => {
112
+ if (props.standalone) {
113
+ return (
114
+ <FullScreenModal
115
+ visible={props.isOpen}
116
+ onClose={() =>
117
+ props.onClickClose?.({
118
+ isOpen: props.isOpen,
119
+ })
120
+ }
121
+ zIndex={props.zIndex}
122
+ >
123
+ <MobileMenuContent {...props} />
124
+ </FullScreenModal>
125
+ );
126
+ }
127
+
128
+ return <MobileMenuContent {...props} />;
129
+ };
130
+
131
+ FullScreenMobileAccordionDrawer.displayName = 'AppHeader.FullScreenMobileAccordionDrawer';
@@ -0,0 +1,50 @@
1
+ 'use client';
2
+
3
+ import { Text } from '@/base';
4
+ import { AppLogo } from '@/extensions';
5
+ import { semantics } from '@/tokens';
6
+ import { media } from '@/utils';
7
+ import { css } from '@emotion/react';
8
+ import styled from '@emotion/styled';
9
+ import { type ReactNode, memo } from 'react';
10
+ import { GlobalLink } from '../global-link';
11
+
12
+ const StyledHeaderLogo = styled(AppLogo)`
13
+ margin-right: 12px;
14
+
15
+ width: 42px;
16
+ height: 42px;
17
+
18
+ ${media.large(css`
19
+ width: 36px;
20
+ height: 36px;
21
+ `)}
22
+ `;
23
+
24
+ const HeaderTitle = styled(Text)`
25
+ font-size: 32px;
26
+ font-weight: 800;
27
+ color: ${semantics.color.foreground[1]};
28
+
29
+ ${media['x-large'](css`
30
+ font-size: 24px;
31
+ `)}
32
+ `;
33
+
34
+ export const AppHeaderLogo = memo(
35
+ ({ logoRightAccessory, title }: { logoRightAccessory?: ReactNode; title?: string }) => {
36
+ return (
37
+ <div style={{ display: 'flex', alignItems: 'center', flex: 1 }}>
38
+ <GlobalLink href="/">
39
+ <StyledHeaderLogo type="round" logoTheme="white-background" />
40
+ </GlobalLink>
41
+ <GlobalLink href="/">
42
+ <HeaderTitle as="h1">
43
+ {title ? `${title} ` : ''}
44
+ {logoRightAccessory}
45
+ </HeaderTitle>
46
+ </GlobalLink>
47
+ </div>
48
+ );
49
+ }
50
+ );
@@ -0,0 +1,69 @@
1
+ 'use client';
2
+
3
+ import { Accordion, type AccordionProps } from '@/extensions';
4
+ import { semantics } from '@/tokens';
5
+ import { usePreventScrollEffect } from '@/utils/use-prevent-scroll-effect';
6
+ import styled from '@emotion/styled';
7
+ import type { ReactNode } from 'react';
8
+
9
+ const ModalContainer = styled.div<{ $isOpen: boolean; $zIndex?: number }>`
10
+ position: fixed;
11
+ top: 0;
12
+ left: 0;
13
+ right: 0;
14
+ bottom: 0;
15
+ background-color: rgba(0, 0, 0, 0.5);
16
+ backdrop-filter: blur(0.5px);
17
+ display: ${({ $isOpen }) => ($isOpen ? 'block' : 'none')};
18
+ z-index: ${(props) => props.$zIndex ?? 100};
19
+ `;
20
+
21
+ const ModalContent = styled.div`
22
+ margin: 10px 0;
23
+
24
+ display: flex;
25
+ flex-direction: column;
26
+ gap: 16px;
27
+ `;
28
+
29
+ const ModalPaper = styled.div`
30
+ background: ${semantics.color.background[2]};
31
+ border-radius: 8px;
32
+ margin: 12px auto;
33
+ width: calc(100vw - 24px);
34
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
35
+ `;
36
+
37
+ type ModalMobileDrawerProps<ItemT> = {
38
+ isOpen: boolean;
39
+ onClose: () => void;
40
+ bottomAccessory?: ReactNode;
41
+ } & AccordionProps<ItemT>;
42
+
43
+ export const ModalMobileAccordionDrawer = <ItemT extends { accordionKey: string }>({
44
+ isOpen,
45
+ onClose,
46
+ bottomAccessory,
47
+ ...accordionProps
48
+ }: ModalMobileDrawerProps<ItemT>) => {
49
+ usePreventScrollEffect({
50
+ shouldPrevent: isOpen,
51
+ });
52
+
53
+ return (
54
+ <ModalContainer onClick={onClose} $isOpen={isOpen} style={{ overflowY: 'auto' }}>
55
+ {isOpen && (
56
+ <ModalPaper onClick={(e) => e.stopPropagation()}>
57
+ <ModalContent>
58
+ <ul style={{ listStyleType: 'none', padding: 0, margin: '10px 20px' }}>
59
+ <Accordion {...accordionProps} />
60
+ </ul>
61
+ {bottomAccessory}
62
+ </ModalContent>
63
+ </ModalPaper>
64
+ )}
65
+ </ModalContainer>
66
+ );
67
+ };
68
+
69
+ ModalMobileAccordionDrawer.displayName = 'AppHeader.ModalMobileAccordionDrawer';