Skip to content

Commit 3d1b529

Browse files
committed
Dock multipage animation updated
1 parent 36480a2 commit 3d1b529

File tree

4 files changed

+189
-102
lines changed

4 files changed

+189
-102
lines changed

src/webOS/components/Dock/Dock.jsx

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// src/components/Dock/Dock.jsx
2+
13
import React, { useState, useEffect, useLayoutEffect } from 'react';
24
import { useDock } from '../../interactions/useDock/useDock.jsx';
35
import { AppsContext } from '../../contexts/AppsContext/AppsContext.jsx';
@@ -16,9 +18,45 @@ import {
1618
getTooltipArrowStyle,
1719
getOpenIndicatorStyle,
1820
} from './DockStyle';
19-
import disabledOverlay from '../../media/icons/disable.png'; // <- your 30%-opacity overlay image
21+
import disabledOverlay from '../../media/icons/disable.png'; // your 30%-opacity overlay image
2022

2123
export default function Dock() {
24+
// 1) Synchronously detect portrait-mobile on first render:
25+
const [isMobilePortrait, setIsMobilePortrait] = useState(() => {
26+
if (typeof window === 'undefined' || !window.matchMedia) {
27+
return false;
28+
}
29+
return window.matchMedia(
30+
'(max-width: 768px) and (orientation: portrait)'
31+
).matches;
32+
});
33+
34+
// 2) Keep it up-to-date on resize/orientation changes:
35+
useEffect(() => {
36+
if (!window.matchMedia) return;
37+
const mql = window.matchMedia('(max-width: 768px) and (orientation: portrait)');
38+
const handler = (e) => setIsMobilePortrait(e.matches);
39+
40+
if (mql.addEventListener) {
41+
mql.addEventListener('change', handler);
42+
} else {
43+
mql.addListener(handler);
44+
}
45+
return () => {
46+
if (mql.removeEventListener) {
47+
mql.removeEventListener('change', handler);
48+
} else {
49+
mql.removeListener(handler);
50+
}
51+
};
52+
}, []);
53+
54+
// 3) Choose the effective config each render:
55+
const effectiveConfig = isMobilePortrait
56+
? { ...DOCK_CONFIG, ...DOCK_CONFIG.vertical }
57+
: DOCK_CONFIG;
58+
59+
// 4) Always call the hook in the same place:
2260
const {
2361
outerRef,
2462
iconsContainerRef,
@@ -56,18 +94,21 @@ export default function Dock() {
5694
openApp,
5795
handleIconMouseEnter,
5896
handleIconMouseLeave,
59-
setCurrentPage,
97+
setCurrentPage, // now our animated changer
6098
} = useDock({
6199
AppsContext,
62-
DOCK_CONFIG,
100+
DOCK_CONFIG: effectiveConfig,
63101
useDeviceInfo,
64102
useStateManager,
65103
useLogger,
66104
});
67105

106+
// rest of your state+effects unchanged:
107+
68108
const [fadeVisible, setFadeVisible] = useState(isDockVisible);
69109
const [toolbarOffset, setToolbarOffset] = useState(0);
70110

111+
// adjust for on-screen keyboard
71112
useLayoutEffect(() => {
72113
const updateOffset = () => {
73114
if (window.visualViewport) {
@@ -84,6 +125,7 @@ export default function Dock() {
84125
};
85126
}, []);
86127

128+
// fade in/out
87129
useEffect(() => {
88130
let timer;
89131
if (isDockVisible) {
@@ -94,6 +136,7 @@ export default function Dock() {
94136
return () => clearTimeout(timer);
95137
}, [isDockVisible]);
96138

139+
// compute outer style
97140
const outerStyle = {
98141
...getOuterContainerStyle(DOCK_POSITION, DOCK_MARGIN, isDockVisible),
99142
opacity: fadeVisible ? 1 : 0,
@@ -103,14 +146,23 @@ export default function Dock() {
103146
outerStyle.bottom = `calc(${toolbarOffset + DOCK_MARGIN}px + env(safe-area-inset-bottom))`;
104147
}
105148

149+
const DOT_SIZE = 8;
150+
106151
return (
107152
<div
108153
ref={outerRef}
109154
style={outerStyle}
110155
onTouchStart={paginationEnabled ? handleTouchStart : null}
111156
onTouchEnd={paginationEnabled ? handleTouchEnd : null}
112157
>
113-
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
158+
<div
159+
style={{
160+
display: 'flex',
161+
flexDirection: 'column',
162+
alignItems: 'center',
163+
position: 'relative',
164+
}}
165+
>
114166
<div
115167
ref={iconsContainerRef}
116168
style={getIconsContainerStyle(isVerticalDock, DOCK_POSITION, ICON_SIZE, containerDimension)}
@@ -138,7 +190,7 @@ export default function Dock() {
138190
NO_TRANSITION,
139191
DOCK_POSITION,
140192
}),
141-
position: 'relative', // allow overlay
193+
position: 'relative',
142194
}}
143195
onClick={() => openApp(app)}
144196
onMouseEnter={() => handleIconMouseEnter(app.id)}
@@ -160,10 +212,8 @@ export default function Dock() {
160212
</div>
161213
)}
162214

163-
{/* App icon */}
164215
<img src={app.icon} alt={app.name} style={iconImageStyle} />
165216

166-
{/* Disabled-overlay */}
167217
{!app.available && (
168218
<img
169219
src={disabledOverlay}
@@ -179,25 +229,31 @@ export default function Dock() {
179229
/>
180230
)}
181231

182-
{/* Open-indicator dot (disabled by default) */}
183-
{/*
184-
{openedApps.includes(app.id) && (
185-
<div style={getOpenIndicatorStyle(DOCK_POSITION)} />
186-
)}
187-
*/}
232+
{activeApp === app.id && (
233+
<div style={getOpenIndicatorStyle(DOCK_POSITION)} />
234+
)}
188235
</div>
189236
))}
190237
</div>
191238

192239
{paginationEnabled && totalPages > 1 && (
193-
<div style={{ marginTop: `${DOTS_MARGIN_BOTTOM}px`, display: 'flex', justifyContent: 'center' }}>
240+
<div
241+
style={{
242+
position: 'absolute',
243+
bottom: `-${DOTS_MARGIN_BOTTOM + DOT_SIZE}px`,
244+
left: 0,
245+
right: 0,
246+
display: 'flex',
247+
justifyContent: 'center',
248+
}}
249+
>
194250
{Array.from({ length: totalPages }).map((_, idx) => (
195251
<div
196252
key={idx}
197253
onClick={() => setCurrentPage(idx)}
198254
style={{
199-
width: '8px',
200-
height: '8px',
255+
width: `${DOT_SIZE}px`,
256+
height: `${DOT_SIZE}px`,
201257
borderRadius: '50%',
202258
margin: '0 4px',
203259
background: idx === currentPage ? 'white' : 'black',

src/webOS/components/Dock/DockStyle.jsx

Lines changed: 39 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// src/components/Dock/DockStyle/DockStyle.jsx
22

3-
// make sure the CSS variables are loaded
4-
import '../../configs/CSSConfigs/animations.css';
3+
// flattened cubic-bezier easing used everywhere
4+
const FLATTENED_EASING = 'cubic-bezier(0.4, 0.0, 0.2, 1.0)';
55

66
/**
77
* Safely include both modern and legacy iOS bottom safe-area insets.
@@ -12,41 +12,43 @@ const SAFE_BOTTOM_CONSTANT = 'constant(safe-area-inset-bottom)';
1212
const withSafeBottom = (marginPx) =>
1313
`calc(${marginPx}px + ${SAFE_BOTTOM_CONSTANT} + ${SAFE_BOTTOM_ENV})`;
1414

15-
// slide-only transition, no opacity here
15+
/**
16+
* Returns the outer container style, including only positioning—
17+
* all animations now come exclusively from useDock's INITIAL_TRANSITION.
18+
*/
1619
export const getOuterContainerStyle = (
1720
DOCK_POSITION,
1821
DOCK_MARGIN,
1922
isDockVisible
2023
) => {
2124
const style = {
22-
// 🔑 fixed to viewport so Safari toolbar doesn’t cover it
2325
position: 'fixed',
2426
zIndex: 200,
25-
transition: 'transform 0.3s var(--easing-flattened)',
27+
// no transition here—use only useDock’s INITIAL_TRANSITION
2628
};
2729

2830
if (DOCK_POSITION === 'bottom') {
29-
style.bottom = withSafeBottom(DOCK_MARGIN);
30-
style.left = '50%';
31+
style.bottom = withSafeBottom(DOCK_MARGIN);
32+
style.left = '50%';
3133
style.transform = isDockVisible
3234
? 'translateX(-50%)'
3335
: 'translateX(-50%) translateY(calc(150% + 10px))';
3436
} else if (DOCK_POSITION === 'left') {
35-
style.left = `${DOCK_MARGIN}px`;
36-
style.top = '50%';
37+
style.left = `${DOCK_MARGIN}px`;
38+
style.top = '50%';
3739
style.transform = isDockVisible
3840
? 'translateY(-50%)'
3941
: 'translateX(calc(-150% - 10px)) translateY(-50%)';
4042
} else if (DOCK_POSITION === 'right') {
41-
style.right = `${DOCK_MARGIN}px`;
42-
style.top = '50%';
43+
style.right = `${DOCK_MARGIN}px`;
44+
style.top = '50%';
4345
style.transform = isDockVisible
4446
? 'translateY(-50%)'
4547
: 'translateX(calc(150% + 10px)) translateY(-50%)';
4648
} else {
47-
// fallback: bottom
48-
style.bottom = withSafeBottom(DOCK_MARGIN);
49-
style.left = '50%';
49+
// fallback to bottom
50+
style.bottom = withSafeBottom(DOCK_MARGIN);
51+
style.left = '50%';
5052
style.transform = isDockVisible
5153
? 'translateX(-50%)'
5254
: 'translateX(-50%) translateY(calc(150% + 10px))';
@@ -55,61 +57,44 @@ export const getOuterContainerStyle = (
5557
return style;
5658
};
5759

60+
/**
61+
* Returns the icons container style. We add a width transition here
62+
* so horizontal docks animate their width smoothly.
63+
*/
5864
export const getIconsContainerStyle = (
5965
isVerticalDock,
6066
DOCK_POSITION,
6167
ICON_SIZE,
6268
containerDimension
6369
) => {
6470
if (isVerticalDock) {
65-
switch (DOCK_POSITION) {
66-
case 'left':
67-
return {
68-
position: 'relative',
69-
display: 'flex',
70-
flexDirection: 'column',
71-
justifyContent: 'center',
72-
alignItems: 'flex-start',
73-
width: `${ICON_SIZE}px`,
74-
height: `${containerDimension}px`,
75-
transition:
76-
'width 0.3s var(--easing-flattened), height 0.3s var(--easing-flattened)',
77-
};
78-
case 'right':
79-
return {
80-
position: 'relative',
81-
display: 'flex',
82-
flexDirection: 'column',
83-
justifyContent: 'center',
84-
alignItems: 'flex-end',
85-
width: `${ICON_SIZE}px`,
86-
height: `${containerDimension}px`,
87-
transition:
88-
'width 0.3s var(--easing-flattened), height 0.3s var(--easing-flattened)',
89-
};
90-
default:
91-
return {
92-
position: 'relative',
93-
display: 'flex',
94-
flexDirection: 'column',
95-
justifyContent: 'center',
96-
alignItems: 'center',
97-
width: `${ICON_SIZE}px`,
98-
height: `${containerDimension}px`,
99-
transition:
100-
'width 0.3s var(--easing-flattened), height 0.3s var(--easing-flattened)',
101-
};
71+
// vertical docks still resize height instantly, but width is fixed
72+
const base = {
73+
position: 'relative',
74+
display: 'flex',
75+
flexDirection: 'column',
76+
justifyContent: 'center',
77+
width: `${ICON_SIZE}px`,
78+
height: `${containerDimension}px`,
79+
// no width transition needed in vertical mode
80+
};
81+
if (DOCK_POSITION === 'left') {
82+
return { ...base, alignItems: 'flex-start' };
83+
} else if (DOCK_POSITION === 'right') {
84+
return { ...base, alignItems: 'flex-end' };
85+
} else {
86+
return { ...base, alignItems: 'center' };
10287
}
10388
} else {
89+
// horizontal dock: width changes as containerDimension updates
10490
return {
10591
position: 'relative',
10692
display: 'flex',
10793
justifyContent: 'center',
10894
alignItems: 'flex-end',
10995
width: `${containerDimension}px`,
11096
height: `${ICON_SIZE}px`,
111-
transition:
112-
'width 0.3s var(--easing-flattened), height 0.3s var(--easing-flattened)',
97+
transition: `width 0.3s ${FLATTENED_EASING}`,
11398
};
11499
}
115100
};
@@ -190,7 +175,7 @@ export const getIconContainerStyle = ({
190175
width: `${ICON_SIZE}px`,
191176
height: `${ICON_SIZE}px`,
192177
transition: shouldTransition
193-
? `${INITIAL_TRANSITION}, opacity 0.3s var(--easing-flattened)`
178+
? `${INITIAL_TRANSITION}, opacity 0.3s ${FLATTENED_EASING}`
194179
: NO_TRANSITION,
195180
transform: `scale(${scale})`,
196181
cursor: 'pointer',

src/webOS/configs/DockConfig/DockConfig.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const DOCK_CONFIG = {
3434
INITIAL_TRANSITION: 'all 0.15s ease',
3535
NO_TRANSITION: 'none',
3636
DOCK_POSITION: 'bottom',
37-
DOCK_MARGIN: 20, // ← was 5, bumped to 20 to avoid shift
37+
DOCK_MARGIN: 30, // ← was 5, bumped to 20 to avoid shift
3838
DOTS_MARGIN_BOTTOM: 20,
3939
APP_NAME_TOOLTIP_OFFSET: 25,
4040
APP_NAME_BACKGROUND_PADDING: '5px 15px',

0 commit comments

Comments
 (0)