@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,160 @@
1
+ import { Text } from '@/base';
2
+ import { AnimatedHeader } from '@/extensions/app-header/app-header';
3
+ import { semantics } from '@/tokens';
4
+ import { media } from '@/utils';
5
+ import { css } from '@emotion/react';
6
+ import styled from '@emotion/styled';
7
+ import { motion } from 'framer-motion';
8
+ import { X as CloseIcon, Menu } from 'lucide-react';
9
+ import { GlobalLink } from '../global-link';
10
+
11
+ export const StyledFloatingHeader = styled(AnimatedHeader)`
12
+ padding-left: 96px;
13
+ padding-right: 96px;
14
+ padding-top: 48px;
15
+ width: 100%;
16
+ display: flex;
17
+ justify-content: center;
18
+
19
+ ${media['xx-large'](css`
20
+ padding-left: 48px;
21
+ padding-right: 48px;
22
+ padding-top: 48px;
23
+ `)}
24
+ ${media['x-large'](css`
25
+ padding-left: 24px;
26
+ padding-right: 24px;
27
+ padding-top: 16px;
28
+ `)}
29
+ ${media.large(css`
30
+ padding-left: 16px;
31
+ padding-right: 16px;
32
+ padding-top: 16px;
33
+ `)};
34
+ `;
35
+
36
+ export const StyledFloatingHeaderInner = styled.div`
37
+ max-width: 1728px;
38
+ width: 100%;
39
+ background-color: ${semantics.color.background[3]};
40
+ border-radius: 10000px;
41
+ z-index: 2;
42
+
43
+ height: 78px;
44
+
45
+ display: flex;
46
+ align-items: center;
47
+
48
+ padding: 12px;
49
+
50
+ ${media.large(css`
51
+ max-width: 1536px;
52
+ `)}
53
+
54
+ ${media.medium(css`
55
+ max-width: none;
56
+ width: 100%;
57
+ `)}
58
+ `;
59
+
60
+ export const StyledFloatingHeaderLogoWrapper = styled(GlobalLink)`
61
+ padding-left: 32px;
62
+ margin-right: 48px;
63
+ display: flex;
64
+ align-items: center;
65
+
66
+ ${media.large(css`
67
+ padding-left: 18px;
68
+ margin-right: 0;
69
+ `)}
70
+
71
+ ${media.small(css`
72
+ padding-left: 0;
73
+ `)}
74
+ `;
75
+
76
+ export const StyledFloatingHeaderAppLogoText = styled(Text)`
77
+ display: block;
78
+ font-size: 24px;
79
+ font-weight: bold;
80
+ margin: unset;
81
+ color: ${semantics.color.foreground[3]};
82
+ ${media.large(css`
83
+ display: none;
84
+ `)}
85
+ `;
86
+
87
+ export const StyledFloatingHeaderMenuContainer = styled.div`
88
+ display: block;
89
+ display: flex;
90
+ align-items: center;
91
+ ${media.large(css`
92
+ display: none;
93
+ `)}
94
+ `;
95
+
96
+ export const StyledFloatingHeaderColorSchemeToggleContainer = styled.div`
97
+ margin-left: auto;
98
+
99
+ ${media.large(css`
100
+ display: none;
101
+ `)}
102
+ `;
103
+
104
+ export const StyledFloatingHeaderCloseDrawerButton = styled.button<{ $isOpen: boolean }>`
105
+ display: none;
106
+ background: ${(props) => (props.$isOpen ? semantics.color.background[1] : semantics.color.background[3])};
107
+ ${media['x-large'](css`
108
+ display: block;
109
+ margin-left: auto;
110
+ cursor: pointer;
111
+
112
+ border: none;
113
+ cursor: pointer;
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+
118
+ min-width: 42px;
119
+ min-height: 42px;
120
+ border-radius: 50%;
121
+ `)}
122
+
123
+ ${media.small(css`
124
+ margin-right: 0px;
125
+ `)}
126
+ `;
127
+
128
+ export const StyledFloatingHeaderOpenDrawerMenu = styled(Menu)`
129
+ display: none;
130
+ ${media.large(css`
131
+ display: block;
132
+ color: ${semantics.color.foreground[3]};
133
+ `)}
134
+ `;
135
+
136
+ export const StyledFloatingHeaderCloseDrawerIcon = styled(CloseIcon)`
137
+ display: none;
138
+ ${media['x-large'](css`
139
+ display: block;
140
+ color: ${semantics.color.foreground[3]};
141
+ `)}
142
+ `;
143
+
144
+ export const StyledFullScreenMobileMenuBackground = styled(motion.div)<{ $standalone?: boolean }>`
145
+ position: fixed;
146
+ top: 0;
147
+ right: 0;
148
+ width: 100%;
149
+ height: 100%;
150
+ background-color: ${semantics.color.background[4]};
151
+ display: flex;
152
+ flex-direction: column;
153
+ z-index: 98;
154
+
155
+ padding-top: ${({ $standalone }) => ($standalone ? '26px' : '126px')};
156
+ padding-left: 16px;
157
+ padding-right: 16px;
158
+
159
+ overflow-y: auto;
160
+ `;
@@ -0,0 +1,91 @@
1
+ 'use client';
2
+
3
+ import { AppLogo } from '@/extensions';
4
+ import type { AnimatedHeaderProps } from '@/extensions/app-header/app-header';
5
+ import {
6
+ useHeaderScrollAnimation,
7
+ useIsMobileMenuOpen,
8
+ } from '@/extensions/app-header/app-header.hooks';
9
+ import styled from '@emotion/styled';
10
+ import { type ReactNode, memo, useCallback } from 'react';
11
+ import {
12
+ StyledFloatingHeader,
13
+ StyledFloatingHeaderAppLogoText,
14
+ StyledFloatingHeaderCloseDrawerButton,
15
+ StyledFloatingHeaderCloseDrawerIcon,
16
+ StyledFloatingHeaderColorSchemeToggleContainer,
17
+ StyledFloatingHeaderInner,
18
+ StyledFloatingHeaderLogoWrapper,
19
+ StyledFloatingHeaderMenuContainer,
20
+ StyledFloatingHeaderOpenDrawerMenu,
21
+ } from './app-header.styled';
22
+
23
+ const StyledFloatingHeaderAppLogo = styled(AppLogo)`
24
+ border-radius: 50%;
25
+ margin-right: 20px;
26
+ width: 48px;
27
+ height: 48px;
28
+ `;
29
+
30
+ type FloatingHeaderProps = {
31
+ serviceName: string;
32
+ HeaderMenuItemComponent: ReactNode;
33
+ ColorSchemeToggleComponent: ReactNode;
34
+ onClickOpenMobileDrawer?: (params: { isMobileMenuOpen: boolean }) => void;
35
+ className?: string;
36
+ zIndex?: AnimatedHeaderProps['zIndex'];
37
+ };
38
+
39
+ export const FloatingHeader = memo(
40
+ ({
41
+ serviceName,
42
+ HeaderMenuItemComponent,
43
+ ColorSchemeToggleComponent,
44
+ onClickOpenMobileDrawer,
45
+ className,
46
+ zIndex,
47
+ }: FloatingHeaderProps) => {
48
+ const { headerAnimation } = useHeaderScrollAnimation();
49
+ const { isMobileMenuOpen, openMobileMenu, closeMobileMenu } = useIsMobileMenuOpen();
50
+
51
+ const handleClickOpenDrawer = useCallback(() => {
52
+ onClickOpenMobileDrawer?.({
53
+ isMobileMenuOpen,
54
+ });
55
+ if (isMobileMenuOpen) {
56
+ closeMobileMenu();
57
+ } else {
58
+ openMobileMenu();
59
+ }
60
+ }, [closeMobileMenu, isMobileMenuOpen, onClickOpenMobileDrawer, openMobileMenu]);
61
+
62
+ return (
63
+ <StyledFloatingHeader animation={headerAnimation} className={className} zIndex={zIndex}>
64
+ <StyledFloatingHeaderInner>
65
+ <StyledFloatingHeaderLogoWrapper href="/">
66
+ <StyledFloatingHeaderAppLogo type="round" logoTheme="white-background" />
67
+ <StyledFloatingHeaderAppLogoText as="h2">{serviceName}</StyledFloatingHeaderAppLogoText>
68
+ </StyledFloatingHeaderLogoWrapper>
69
+ <StyledFloatingHeaderMenuContainer>
70
+ {HeaderMenuItemComponent}
71
+ </StyledFloatingHeaderMenuContainer>
72
+ <StyledFloatingHeaderColorSchemeToggleContainer>
73
+ {ColorSchemeToggleComponent}
74
+ </StyledFloatingHeaderColorSchemeToggleContainer>
75
+ <StyledFloatingHeaderCloseDrawerButton
76
+ $isOpen={isMobileMenuOpen}
77
+ onClick={handleClickOpenDrawer}
78
+ >
79
+ {isMobileMenuOpen ? (
80
+ <StyledFloatingHeaderCloseDrawerIcon />
81
+ ) : (
82
+ <StyledFloatingHeaderOpenDrawerMenu />
83
+ )}
84
+ </StyledFloatingHeaderCloseDrawerButton>
85
+ </StyledFloatingHeaderInner>
86
+ </StyledFloatingHeader>
87
+ );
88
+ }
89
+ );
90
+
91
+ FloatingHeader.displayName = 'AppHeader.FloatingHeader';
@@ -0,0 +1,13 @@
1
+ import { AppHeader as AppHeaderDefault } from '@/extensions';
2
+ import { FloatingHeader } from './app-header';
3
+ import { FixedHeader } from './app-header.fixed-header';
4
+ import { FullScreenMobileAccordionDrawer } from './app-header.full-screen-mobile-accordion-drawer';
5
+ import { ModalMobileAccordionDrawer } from './app-header.modal-mobile-accordion-drawer';
6
+
7
+ export const AppHeader = {
8
+ ...AppHeaderDefault,
9
+ FloatingHeader,
10
+ FixedHeader,
11
+ FullScreenMobileAccordionDrawer,
12
+ ModalMobileAccordionDrawer,
13
+ };
@@ -0,0 +1,41 @@
1
+ import { useSyncExternalStore } from 'react';
2
+
3
+ type LinkStoreState = {
4
+ isLoading: boolean;
5
+ };
6
+
7
+ type Listener = () => void;
8
+
9
+ let state: LinkStoreState = {
10
+ isLoading: false,
11
+ };
12
+
13
+ const listeners = new Set<Listener>();
14
+
15
+ const linkStore = {
16
+ getSnapshot: () => state,
17
+
18
+ subscribe: (listener: Listener) => {
19
+ listeners.add(listener);
20
+ return () => listeners.delete(listener);
21
+ },
22
+
23
+ setIsLoading: (isLoading: boolean) => {
24
+ if (state.isLoading === isLoading) return;
25
+ state = { ...state, isLoading };
26
+ listeners.forEach((l) => l());
27
+ },
28
+ };
29
+
30
+ export function useLinkStore() {
31
+ const state = useSyncExternalStore(
32
+ linkStore.subscribe,
33
+ linkStore.getSnapshot,
34
+ linkStore.getSnapshot // SSR fallback
35
+ );
36
+
37
+ return {
38
+ isLoading: state.isLoading,
39
+ setIsLoading: linkStore.setIsLoading,
40
+ };
41
+ }
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+
3
+ import { fullyDecodePathname } from '@coldsurfers/shared-utils';
4
+ import Link, { type LinkProps } from 'next/link';
5
+ import { usePathname } from 'next/navigation';
6
+ import {
7
+ type AnchorHTMLAttributes,
8
+ type MouseEventHandler,
9
+ type PointerEventHandler,
10
+ type PropsWithChildren,
11
+ useCallback,
12
+ } from 'react';
13
+ import { useLinkStore } from './global-link.store';
14
+ import { getRedirectHref } from './global-link.utils';
15
+
16
+ export function GlobalLink({
17
+ children,
18
+ href,
19
+ onClick,
20
+ target,
21
+ ...otherProps
22
+ }: PropsWithChildren<LinkProps & AnchorHTMLAttributes<HTMLAnchorElement>>) {
23
+ const pathname = usePathname();
24
+ const { setIsLoading } = useLinkStore();
25
+ const handleClick = useCallback<MouseEventHandler<HTMLAnchorElement>>(
26
+ (e) => {
27
+ const to = getRedirectHref(href);
28
+ const from = fullyDecodePathname(pathname);
29
+ if (!target && to !== from) {
30
+ setIsLoading(true);
31
+ }
32
+ onClick?.(e);
33
+ },
34
+ [onClick, pathname, setIsLoading, target, href]
35
+ );
36
+ const onPointerDown = useCallback<PointerEventHandler<HTMLAnchorElement>>((e) => {
37
+ e.preventDefault();
38
+ }, []);
39
+
40
+ return (
41
+ <Link
42
+ href={href}
43
+ target={target}
44
+ onClick={handleClick}
45
+ {...otherProps}
46
+ // or draggable={false}
47
+ onPointerDown={onPointerDown}
48
+ >
49
+ {children}
50
+ </Link>
51
+ );
52
+ }
@@ -0,0 +1,9 @@
1
+ let redirectMap: Record<string, string> = {};
2
+
3
+ export function initializeGlobalLinkRedirectMap(map: Record<string, string>) {
4
+ redirectMap = map;
5
+ }
6
+
7
+ export function getRedirectHref(href: string) {
8
+ return redirectMap[href] ?? href;
9
+ }
@@ -0,0 +1,3 @@
1
+ export * from './global-link';
2
+ export * from './global-link.store';
3
+ export { initializeGlobalLinkRedirectMap } from './global-link.utils';
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+ import {
4
+ type GridCardListItemProps,
5
+ MasonryGridCardItem as MasonryGridCardItemUI,
6
+ } from '@/extensions/grid-card-item';
7
+ import { memo } from 'react';
8
+ import { GlobalLink } from '../global-link';
9
+
10
+ export const MasonryGridCardItem = memo(
11
+ ({ href, onClick, ...gridCardListItemProps }: GridCardListItemProps) => {
12
+ if (!href) {
13
+ return <MasonryGridCardItemUI onClick={onClick} {...gridCardListItemProps} />;
14
+ }
15
+ return (
16
+ <GlobalLink href={href} onClick={onClick}>
17
+ <MasonryGridCardItemUI {...gridCardListItemProps} />
18
+ </GlobalLink>
19
+ );
20
+ }
21
+ );
22
+
23
+ MasonryGridCardItem.displayName = 'GridCardList.MasonryItem';
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+ import {
4
+ GridCardItem as GridCardItemUI,
5
+ type GridCardListItemProps,
6
+ } from '@/extensions/grid-card-item';
7
+ import { memo } from 'react';
8
+ import { GlobalLink } from '../global-link';
9
+
10
+ export const GridCardItem = memo(
11
+ ({ href, onClick, ...gridCardListItemProps }: GridCardListItemProps) => {
12
+ if (!href) {
13
+ return <GridCardItemUI onClick={onClick} {...gridCardListItemProps} />;
14
+ }
15
+ return (
16
+ <GlobalLink href={href} onClick={onClick}>
17
+ <GridCardItemUI {...gridCardListItemProps} />
18
+ </GlobalLink>
19
+ );
20
+ }
21
+ );
22
+
23
+ GridCardItem.displayName = 'GridCardList.Item';
@@ -0,0 +1,2 @@
1
+ export * from './grid-card-item';
2
+ export * from './grid-card-item.masonry';
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+
3
+ import { GridCardList as DefaultGridCardList } from '@/extensions';
4
+ import { GridCardItem, MasonryGridCardItem } from './grid-card-item';
5
+
6
+ export const GridCardList = {
7
+ ...DefaultGridCardList,
8
+ Item: GridCardItem,
9
+ MasonryItem: MasonryGridCardItem,
10
+ };
11
+
12
+ export * from './global-link';
13
+ export * from './route-loading';
14
+ export * from './new-tab-link';
15
+ export * from './app-footer';
16
+ export * from './app-header';
@@ -0,0 +1 @@
1
+ export * from './new-tab-link';
@@ -0,0 +1,15 @@
1
+ import type { LinkProps } from 'next/link';
2
+ import Link from 'next/link';
3
+ import { type PropsWithChildren, memo } from 'react';
4
+
5
+ export const NewTabLink = memo(
6
+ ({ children, ...linkProps }: PropsWithChildren<Omit<LinkProps, 'target' | 'rel'>>) => {
7
+ return (
8
+ <Link target="_blank" rel="noopener noreferrer" {...linkProps}>
9
+ {children}
10
+ </Link>
11
+ );
12
+ }
13
+ );
14
+
15
+ NewTabLink.displayName = 'NewTabLink';
@@ -0,0 +1 @@
1
+ export * from './route-loading';
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+
3
+ import { Spinner } from '@/base';
4
+ import { type DependencyList, type PropsWithChildren, Suspense, useLayoutEffect } from 'react';
5
+ import { useLinkStore } from '../global-link';
6
+
7
+ export function RouteLoading({ children, deps }: PropsWithChildren<{ deps?: DependencyList }>) {
8
+ const { setIsLoading, isLoading } = useLinkStore();
9
+
10
+ useLayoutEffect(() => {
11
+ setIsLoading(false);
12
+ // eslint-disable-next-line react-hooks/exhaustive-deps
13
+ }, [setIsLoading, ...(deps ?? [])]);
14
+
15
+ return (
16
+ <Suspense fallback={<Spinner variant="page-overlay" />}>
17
+ {children}
18
+ {isLoading && <Spinner variant="page-overlay" />}
19
+ </Suspense>
20
+ );
21
+ }
@@ -0,0 +1,2 @@
1
+ export * from './tokens';
2
+ export * from './tokens.types';
@@ -0,0 +1,8 @@
1
+ import colorDesignTokens from '@coldsurfers/ocean-road-design-tokens/js/color/variables';
2
+ import themeVariablesDesignTokens from '@coldsurfers/ocean-road-design-tokens/js/semantic/theme-variables';
3
+ import semanticDesignTokens from '@coldsurfers/ocean-road-design-tokens/js/semantic/variables';
4
+ import type { ColorDesignTokens } from './tokens.types';
5
+
6
+ export const colors: ColorDesignTokens = colorDesignTokens;
7
+ export const semantics = semanticDesignTokens;
8
+ export const semanticVariables = themeVariablesDesignTokens;
@@ -0,0 +1,7 @@
1
+ import type darkColorDesignTokens from '@coldsurfers/ocean-road-design-tokens/dist/json/color/variables-dark.json';
2
+ import type lightColorDesignTokens from '@coldsurfers/ocean-road-design-tokens/dist/json/color/variables-light.json';
3
+ import type colorDesignTokens from '@coldsurfers/ocean-road-design-tokens/js/color/variables';
4
+
5
+ export type ColorDesignTokens = typeof colorDesignTokens;
6
+ export type DarkColorDesignTokens = typeof darkColorDesignTokens;
7
+ export type LightColorDesignTokens = typeof lightColorDesignTokens;
@@ -0,0 +1,9 @@
1
+ const breakpoints = {
2
+ small: 576,
3
+ medium: 768,
4
+ large: 992,
5
+ 'x-large': 1280,
6
+ 'xx-large': 1460,
7
+ } as const;
8
+
9
+ export default breakpoints;
@@ -0,0 +1,23 @@
1
+ import { css } from '@emotion/react';
2
+ import media from './media';
3
+
4
+ type Edge = 'left' | 'right';
5
+
6
+ export const commonHorizontalLayoutCss = (edges: readonly Edge[]) => {
7
+ return media['xx-large'](css`
8
+ ${edges.includes('left') && 'padding-left: 0.75rem;'}
9
+ ${edges.includes('right') && 'padding-right: 0.75rem;'}
10
+ `);
11
+ };
12
+
13
+ export const commonWebkitScrollHideCss = () => {
14
+ return css`
15
+ scroll-behavior: auto;
16
+ -webkit-overflow-scrolling: touch;
17
+ scrollbar-width: none;
18
+ -webkit-scrollbar-width: none;
19
+ &::-webkit-scrollbar {
20
+ display: none;
21
+ }
22
+ `;
23
+ };
@@ -0,0 +1,2 @@
1
+ export { default as breakpoints } from './breakpoints';
2
+ export { default as media } from './media';
@@ -0,0 +1,23 @@
1
+ import { type SerializedStyles, css } from '@emotion/react';
2
+ import breakpoints from './breakpoints';
3
+
4
+ type BreakpointKey = keyof typeof breakpoints;
5
+
6
+ // Define a type-safe media query utility
7
+ const media: Record<BreakpointKey, (styles: SerializedStyles | string) => SerializedStyles> =
8
+ Object.keys(breakpoints).reduce(
9
+ (acc, label) => {
10
+ const breakpointLabel = label as BreakpointKey;
11
+
12
+ acc[breakpointLabel] = (styles) => css`
13
+ @media (max-width: ${breakpoints[breakpointLabel]}px) {
14
+ ${styles}
15
+ }
16
+ `;
17
+
18
+ return acc;
19
+ },
20
+ {} as Record<BreakpointKey, (styles: SerializedStyles | string) => SerializedStyles>
21
+ );
22
+
23
+ export default media;
@@ -0,0 +1,19 @@
1
+ import { useEffect } from 'react';
2
+
3
+ export const usePreventScrollEffect = ({
4
+ shouldPrevent,
5
+ }: {
6
+ shouldPrevent: boolean;
7
+ }) => {
8
+ useEffect(() => {
9
+ const { body } = document;
10
+ if (shouldPrevent) {
11
+ body.style.overflow = 'hidden'; // Disable scrolling
12
+ } else {
13
+ body.style.overflow = ''; // Reset overflow to enable scrolling
14
+ }
15
+ return () => {
16
+ body.style.overflow = ''; // Clean up on unmount
17
+ };
18
+ }, [shouldPrevent]);
19
+ };
@@ -0,0 +1,3 @@
1
+ export type WithId<T> = {
2
+ id: string;
3
+ } & T;
@@ -0,0 +1,10 @@
1
+ import type { SyntheticEvent } from 'react';
2
+
3
+ export const withStopPropagation = <T extends SyntheticEvent>(handler?: (event: T) => void) => {
4
+ return (event: T) => {
5
+ event.stopPropagation();
6
+ event.preventDefault();
7
+
8
+ handler?.(event);
9
+ };
10
+ };