@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,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,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,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,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,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,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
|
+
};
|