@syscore/ui-library 1.17.0 → 1.18.0
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/client/components/ui/mobile-nav.tsx +93 -62
- package/client/global.css +56 -6
- package/client/ui/MobileNav.stories.tsx +106 -84
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +77 -51
- package/package.json +1 -1
|
@@ -4,11 +4,12 @@ import {
|
|
|
4
4
|
useCallback,
|
|
5
5
|
useMemo,
|
|
6
6
|
useEffect,
|
|
7
|
+
useLayoutEffect,
|
|
7
8
|
ReactNode,
|
|
8
9
|
createContext,
|
|
9
10
|
useContext,
|
|
10
11
|
} from "react";
|
|
11
|
-
import { motion, useMotionValue, animate
|
|
12
|
+
import { motion, useMotionValue, animate } from "motion/react";
|
|
12
13
|
import { cn } from "@/lib/utils";
|
|
13
14
|
|
|
14
15
|
interface MobileNavContextValue {
|
|
@@ -99,10 +100,8 @@ const MobileNavPanel = ({ className, children }: MobileNavPanelProps) => {
|
|
|
99
100
|
const heightMV = useMotionValue(0);
|
|
100
101
|
const stopAnimation = useRef<(() => void) | null>(null);
|
|
101
102
|
|
|
102
|
-
//
|
|
103
|
+
// Panel state: "closed" | "initial" | "full"
|
|
103
104
|
const stateRef = useRef<"closed" | "initial" | "full">("closed");
|
|
104
|
-
|
|
105
|
-
// Cache the initial content height so returning from full → initial is consistent
|
|
106
105
|
const cachedInitialHeight = useRef(0);
|
|
107
106
|
|
|
108
107
|
const getNavBarHeight = () => {
|
|
@@ -111,15 +110,12 @@ const MobileNavPanel = ({ className, children }: MobileNavPanelProps) => {
|
|
|
111
110
|
};
|
|
112
111
|
|
|
113
112
|
const getAvailableHeight = () => {
|
|
114
|
-
|
|
115
|
-
const container = parent?.clientHeight ?? window.innerHeight;
|
|
116
|
-
return container - getNavBarHeight();
|
|
113
|
+
return window.innerHeight - getNavBarHeight();
|
|
117
114
|
};
|
|
118
115
|
|
|
119
116
|
const measureContentHeight = () => {
|
|
120
117
|
const content = contentRef.current;
|
|
121
118
|
if (!content) return 0;
|
|
122
|
-
// Measure the first child to get true content height without triggering reflow
|
|
123
119
|
const child = content.firstElementChild as HTMLElement | null;
|
|
124
120
|
const raw = (child?.offsetHeight ?? content.scrollHeight) + 40; // +40 for handle area
|
|
125
121
|
const max = getAvailableHeight() * 0.7;
|
|
@@ -136,78 +132,111 @@ const MobileNavPanel = ({ className, children }: MobileNavPanelProps) => {
|
|
|
136
132
|
};
|
|
137
133
|
|
|
138
134
|
// Animate open/close
|
|
139
|
-
|
|
140
|
-
let cancelled = false;
|
|
135
|
+
useLayoutEffect(() => {
|
|
141
136
|
if (open) {
|
|
142
137
|
stateRef.current = "initial";
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
cachedInitialHeight.current = h;
|
|
147
|
-
springTo(h);
|
|
148
|
-
});
|
|
138
|
+
const h = measureContentHeight();
|
|
139
|
+
cachedInitialHeight.current = h;
|
|
140
|
+
springTo(h);
|
|
149
141
|
} else {
|
|
150
142
|
stateRef.current = "closed";
|
|
151
143
|
cachedInitialHeight.current = 0;
|
|
152
144
|
springTo(0);
|
|
153
145
|
}
|
|
154
|
-
return () => {
|
|
155
|
-
cancelled = true;
|
|
156
|
-
};
|
|
157
146
|
}, [open]);
|
|
158
147
|
|
|
159
|
-
// Re-measure when
|
|
160
|
-
|
|
148
|
+
// Re-measure when switching tabs while panel is already open
|
|
149
|
+
useLayoutEffect(() => {
|
|
161
150
|
if (!open) return;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
cachedInitialHeight.current = h;
|
|
169
|
-
if (stateRef.current === "initial" || stateRef.current === "closed") {
|
|
170
|
-
stateRef.current = "initial";
|
|
171
|
-
springTo(h);
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
return () => {
|
|
176
|
-
cancelled = true;
|
|
177
|
-
};
|
|
151
|
+
const h = measureContentHeight();
|
|
152
|
+
cachedInitialHeight.current = h;
|
|
153
|
+
if (stateRef.current !== "full") {
|
|
154
|
+
stateRef.current = "initial";
|
|
155
|
+
springTo(h);
|
|
156
|
+
}
|
|
178
157
|
}, [activeKey]);
|
|
179
158
|
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
159
|
+
// Close on Escape key
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
if (!open) return;
|
|
162
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
163
|
+
if (e.key === "Escape") close();
|
|
164
|
+
};
|
|
165
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
166
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
167
|
+
}, [open, close]);
|
|
168
|
+
|
|
169
|
+
// --- Pointer-based drag (bypasses motion's gesture detection) ---
|
|
170
|
+
const isDragging = useRef(false);
|
|
171
|
+
const lastY = useRef(0);
|
|
172
|
+
const dragStartY = useRef(0);
|
|
173
|
+
const dragStartTime = useRef(0);
|
|
174
|
+
const dragMaxHeight = useRef(0);
|
|
175
|
+
|
|
176
|
+
const handlePointerDown = (e: React.PointerEvent) => {
|
|
177
|
+
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
|
178
|
+
stopAnimation.current?.();
|
|
179
|
+
isDragging.current = true;
|
|
180
|
+
lastY.current = e.clientY;
|
|
181
|
+
dragStartY.current = e.clientY;
|
|
182
|
+
dragStartTime.current = Date.now();
|
|
183
|
+
dragMaxHeight.current = getAvailableHeight();
|
|
185
184
|
};
|
|
186
185
|
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
186
|
+
const handlePointerMove = (e: React.PointerEvent) => {
|
|
187
|
+
if (!isDragging.current) return;
|
|
188
|
+
const dy = e.clientY - lastY.current;
|
|
189
|
+
lastY.current = e.clientY;
|
|
190
|
+
const current = heightMV.get();
|
|
191
|
+
const next = Math.max(0, current - dy);
|
|
192
|
+
heightMV.set(Math.min(next, dragMaxHeight.current));
|
|
190
193
|
};
|
|
191
194
|
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
+
const handlePointerUp = (e: React.PointerEvent) => {
|
|
196
|
+
if (!isDragging.current) return;
|
|
197
|
+
isDragging.current = false;
|
|
195
198
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
199
|
+
const currentHeight = heightMV.get();
|
|
200
|
+
const initial = cachedInitialHeight.current;
|
|
201
|
+
const full = dragMaxHeight.current;
|
|
202
|
+
|
|
203
|
+
// Calculate velocity (px/s) — positive = downward
|
|
204
|
+
const elapsed = Date.now() - dragStartTime.current;
|
|
205
|
+
const totalDy = e.clientY - dragStartY.current;
|
|
206
|
+
const velocity = elapsed > 0 ? (totalDy / elapsed) * 1000 : 0;
|
|
207
|
+
|
|
208
|
+
const VELOCITY_THRESHOLD = 500;
|
|
209
|
+
|
|
210
|
+
if (velocity > VELOCITY_THRESHOLD) {
|
|
211
|
+
// Fast downward flick
|
|
203
212
|
if (stateRef.current === "full") {
|
|
204
213
|
stateRef.current = "initial";
|
|
205
|
-
springTo(
|
|
206
|
-
} else
|
|
214
|
+
springTo(initial);
|
|
215
|
+
} else {
|
|
207
216
|
stateRef.current = "closed";
|
|
208
217
|
springTo(0);
|
|
209
218
|
close();
|
|
210
219
|
}
|
|
220
|
+
} else if (velocity < -VELOCITY_THRESHOLD) {
|
|
221
|
+
// Fast upward flick
|
|
222
|
+
stateRef.current = "full";
|
|
223
|
+
springTo(full);
|
|
224
|
+
} else {
|
|
225
|
+
// Slow drag — snap to nearest
|
|
226
|
+
const midInitial = initial / 2;
|
|
227
|
+
const midFull = (initial + full) / 2;
|
|
228
|
+
|
|
229
|
+
if (currentHeight < midInitial) {
|
|
230
|
+
stateRef.current = "closed";
|
|
231
|
+
springTo(0);
|
|
232
|
+
close();
|
|
233
|
+
} else if (currentHeight < midFull) {
|
|
234
|
+
stateRef.current = "initial";
|
|
235
|
+
springTo(initial);
|
|
236
|
+
} else {
|
|
237
|
+
stateRef.current = "full";
|
|
238
|
+
springTo(full);
|
|
239
|
+
}
|
|
211
240
|
}
|
|
212
241
|
};
|
|
213
242
|
|
|
@@ -222,14 +251,15 @@ const MobileNavPanel = ({ className, children }: MobileNavPanelProps) => {
|
|
|
222
251
|
data-closed={!open || undefined}
|
|
223
252
|
style={{ height: heightMV }}
|
|
224
253
|
>
|
|
225
|
-
<
|
|
254
|
+
<div
|
|
226
255
|
className="mobile-nav-handle"
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
256
|
+
onPointerDown={handlePointerDown}
|
|
257
|
+
onPointerMove={handlePointerMove}
|
|
258
|
+
onPointerUp={handlePointerUp}
|
|
259
|
+
onPointerCancel={handlePointerUp}
|
|
230
260
|
>
|
|
231
261
|
<div className="mobile-nav-handle-bar" />
|
|
232
|
-
</
|
|
262
|
+
</div>
|
|
233
263
|
|
|
234
264
|
<div ref={contentRef} className="mobile-nav-content">
|
|
235
265
|
{activeKey ? children(activeKey) : null}
|
|
@@ -269,6 +299,7 @@ const MobileNavTrigger = ({
|
|
|
269
299
|
onClick={handleClick}
|
|
270
300
|
disabled={disabled}
|
|
271
301
|
data-active={activeKey === value || undefined}
|
|
302
|
+
aria-expanded={activeKey === value && open}
|
|
272
303
|
className={cn("mobile-nav-trigger group", className)}
|
|
273
304
|
aria-label={label}
|
|
274
305
|
>
|
package/client/global.css
CHANGED
|
@@ -3852,21 +3852,25 @@ body {
|
|
|
3852
3852
|
|
|
3853
3853
|
/* MobileNav Styles */
|
|
3854
3854
|
.mobile-nav {
|
|
3855
|
+
position: fixed;
|
|
3856
|
+
bottom: 0;
|
|
3857
|
+
left: 0;
|
|
3858
|
+
right: 0;
|
|
3859
|
+
z-index: 50;
|
|
3855
3860
|
display: flex;
|
|
3856
3861
|
flex-direction: column;
|
|
3857
|
-
position: relative;
|
|
3858
3862
|
}
|
|
3859
3863
|
|
|
3860
3864
|
.mobile-nav-overlay {
|
|
3861
|
-
position:
|
|
3865
|
+
position: fixed;
|
|
3862
3866
|
inset: 0;
|
|
3863
3867
|
background-color: rgba(0, 0, 0, 0.08);
|
|
3864
|
-
z-index:
|
|
3868
|
+
z-index: 40;
|
|
3865
3869
|
}
|
|
3866
3870
|
|
|
3867
3871
|
.mobile-nav-panel {
|
|
3868
3872
|
position: relative;
|
|
3869
|
-
z-index:
|
|
3873
|
+
z-index: 50;
|
|
3870
3874
|
background-color: white;
|
|
3871
3875
|
border-radius: 32px 32px 0 0;
|
|
3872
3876
|
box-shadow:
|
|
@@ -3906,7 +3910,7 @@ body {
|
|
|
3906
3910
|
|
|
3907
3911
|
.mobile-nav-bar {
|
|
3908
3912
|
position: relative;
|
|
3909
|
-
z-index:
|
|
3913
|
+
z-index: 50;
|
|
3910
3914
|
border-top: 1px solid var(--color-gray-100);
|
|
3911
3915
|
background-color: var(--color-gray-50);
|
|
3912
3916
|
flex-shrink: 0;
|
|
@@ -3920,7 +3924,7 @@ body {
|
|
|
3920
3924
|
align-items: center;
|
|
3921
3925
|
justify-content: space-around;
|
|
3922
3926
|
padding-top: 0.5rem;
|
|
3923
|
-
padding-bottom:
|
|
3927
|
+
padding-bottom: 0.5rem;
|
|
3924
3928
|
padding-left: 1.5rem;
|
|
3925
3929
|
padding-right: 1.5rem;
|
|
3926
3930
|
}
|
|
@@ -6992,6 +6996,38 @@ body {
|
|
|
6992
6996
|
text-underline-offset: 2px;
|
|
6993
6997
|
}
|
|
6994
6998
|
|
|
6999
|
+
/* Responsive Typography - Mobile */
|
|
7000
|
+
@media (max-width: 640px) {
|
|
7001
|
+
.heading-xlarge {
|
|
7002
|
+
font-size: 36px;
|
|
7003
|
+
line-height: 40px;
|
|
7004
|
+
}
|
|
7005
|
+
.heading-large {
|
|
7006
|
+
font-size: 28px;
|
|
7007
|
+
line-height: 32px;
|
|
7008
|
+
}
|
|
7009
|
+
.heading-medium {
|
|
7010
|
+
font-size: 26px;
|
|
7011
|
+
line-height: 30px;
|
|
7012
|
+
}
|
|
7013
|
+
.heading-small {
|
|
7014
|
+
font-size: 24px;
|
|
7015
|
+
line-height: 28px;
|
|
7016
|
+
}
|
|
7017
|
+
.heading-xsmall {
|
|
7018
|
+
font-size: 20px;
|
|
7019
|
+
line-height: 24px;
|
|
7020
|
+
}
|
|
7021
|
+
.heading-xxsmall {
|
|
7022
|
+
font-size: 18px;
|
|
7023
|
+
line-height: 22px;
|
|
7024
|
+
}
|
|
7025
|
+
.body-large {
|
|
7026
|
+
font-size: 16px;
|
|
7027
|
+
line-height: 22.4px;
|
|
7028
|
+
}
|
|
7029
|
+
}
|
|
7030
|
+
|
|
6995
7031
|
/* Standard Table Styles */
|
|
6996
7032
|
.standard-table {
|
|
6997
7033
|
border: 1px solid var(--color-blue-200, #cbe0f1);
|
|
@@ -7105,6 +7141,14 @@ body {
|
|
|
7105
7141
|
gap: 1rem;
|
|
7106
7142
|
}
|
|
7107
7143
|
|
|
7144
|
+
@media (max-width: 640px) {
|
|
7145
|
+
.standard-table-row-header,
|
|
7146
|
+
.standard-table-list-row {
|
|
7147
|
+
height: auto;
|
|
7148
|
+
min-height: 5rem;
|
|
7149
|
+
}
|
|
7150
|
+
}
|
|
7151
|
+
|
|
7108
7152
|
/* PageHeader Styles */
|
|
7109
7153
|
.page-header {
|
|
7110
7154
|
margin-bottom: 1.5rem;
|
|
@@ -7142,6 +7186,12 @@ body {
|
|
|
7142
7186
|
padding-right: 10rem;
|
|
7143
7187
|
}
|
|
7144
7188
|
|
|
7189
|
+
@media (max-width: 640px) {
|
|
7190
|
+
.page-header-description {
|
|
7191
|
+
padding-right: 0;
|
|
7192
|
+
}
|
|
7193
|
+
}
|
|
7194
|
+
|
|
7145
7195
|
/* Navigation Styles */
|
|
7146
7196
|
.navigation {
|
|
7147
7197
|
position: absolute;
|
|
@@ -17,6 +17,7 @@ import { UtilityText } from "../components/icons/UtilityText";
|
|
|
17
17
|
import { UtilityCompare } from "../components/icons/UtilityCompare";
|
|
18
18
|
import { UtilityRevisionsShow } from "../components/icons/UtilityRevisionsShow";
|
|
19
19
|
import { UtilityFeedback } from "../components/icons/UtilityFeedback";
|
|
20
|
+
import { Button } from "@/components/ui/button";
|
|
20
21
|
|
|
21
22
|
const meta = {
|
|
22
23
|
title: "Review/MobileNav",
|
|
@@ -24,6 +25,9 @@ const meta = {
|
|
|
24
25
|
tags: ["autodocs"],
|
|
25
26
|
parameters: {
|
|
26
27
|
layout: "fullscreen",
|
|
28
|
+
viewport: {
|
|
29
|
+
defaultViewport: "mobile1",
|
|
30
|
+
},
|
|
27
31
|
docs: {
|
|
28
32
|
description: {
|
|
29
33
|
component: `
|
|
@@ -41,20 +45,26 @@ Mobile navigation with a spring-animated slide-up panel. Built as a **compound c
|
|
|
41
45
|
## Basic Usage
|
|
42
46
|
|
|
43
47
|
\`\`\`tsx
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
</
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
</
|
|
48
|
+
<section>
|
|
49
|
+
{/* Your page content */}
|
|
50
|
+
<MyPageContent />
|
|
51
|
+
|
|
52
|
+
{/* Fixed bottom nav */}
|
|
53
|
+
<MobileNav>
|
|
54
|
+
<MobileNavPanel>
|
|
55
|
+
{(activeKey) => <MyContent tab={activeKey} />}
|
|
56
|
+
</MobileNavPanel>
|
|
57
|
+
|
|
58
|
+
<MobileNavBar>
|
|
59
|
+
<MobileNavTrigger value="home" label="Home">
|
|
60
|
+
<HomeIcon className="size-6" />
|
|
61
|
+
</MobileNavTrigger>
|
|
62
|
+
<MobileNavTrigger value="search" label="Search">
|
|
63
|
+
<SearchIcon className="size-6" />
|
|
64
|
+
</MobileNavTrigger>
|
|
65
|
+
</MobileNavBar>
|
|
66
|
+
</MobileNav>
|
|
67
|
+
</section>
|
|
58
68
|
\`\`\`
|
|
59
69
|
|
|
60
70
|
## Styling Active State
|
|
@@ -224,35 +234,43 @@ export const Default: Story = {
|
|
|
224
234
|
args: { children: null },
|
|
225
235
|
|
|
226
236
|
render: () => (
|
|
227
|
-
<
|
|
237
|
+
<div className="h-screen w-full bg-white">
|
|
228
238
|
{/* Main content area */}
|
|
229
|
-
<div className="
|
|
239
|
+
<div className="p-6">
|
|
230
240
|
<p className="text-sm text-gray-400">
|
|
231
241
|
Tap an icon below to open the panel.
|
|
232
242
|
</p>
|
|
243
|
+
<Button
|
|
244
|
+
size="large"
|
|
245
|
+
variant="general-primary"
|
|
246
|
+
onClick={() => alert("Hello")}
|
|
247
|
+
>
|
|
248
|
+
Open panel
|
|
249
|
+
</Button>
|
|
233
250
|
</div>
|
|
234
251
|
|
|
235
|
-
{/*
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
252
|
+
{/* Fixed bottom nav */}
|
|
253
|
+
<MobileNav>
|
|
254
|
+
<MobileNavPanel>
|
|
255
|
+
{(activeKey) => <PanelContent activeKey={activeKey} />}
|
|
256
|
+
</MobileNavPanel>
|
|
257
|
+
|
|
258
|
+
<MobileNavBar>
|
|
259
|
+
<MobileNavTrigger value="list" label="Changes">
|
|
260
|
+
<UtilityText className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
261
|
+
</MobileNavTrigger>
|
|
262
|
+
<MobileNavTrigger value="workflow" label="Workflow">
|
|
263
|
+
<UtilityCompare className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
264
|
+
</MobileNavTrigger>
|
|
265
|
+
<MobileNavTrigger value="edit" label="Edits">
|
|
266
|
+
<UtilityRevisionsShow className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
267
|
+
</MobileNavTrigger>
|
|
268
|
+
<MobileNavTrigger value="comments" label="Comments">
|
|
269
|
+
<UtilityFeedback className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
270
|
+
</MobileNavTrigger>
|
|
271
|
+
</MobileNavBar>
|
|
272
|
+
</MobileNav>
|
|
273
|
+
</div>
|
|
256
274
|
),
|
|
257
275
|
};
|
|
258
276
|
|
|
@@ -260,38 +278,40 @@ const WithCustomActionRender = () => {
|
|
|
260
278
|
const [modalOpen, setModalOpen] = useState(false);
|
|
261
279
|
|
|
262
280
|
return (
|
|
263
|
-
<
|
|
264
|
-
<div className="
|
|
281
|
+
<div className="relative h-screen w-full bg-white">
|
|
282
|
+
<div className="p-6">
|
|
265
283
|
<p className="text-sm text-gray-400">
|
|
266
284
|
The “Edits” trigger opens a modal instead of the panel.
|
|
267
285
|
</p>
|
|
268
286
|
</div>
|
|
269
287
|
|
|
270
|
-
<
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
<
|
|
276
|
-
<
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
<
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
<
|
|
290
|
-
|
|
291
|
-
|
|
288
|
+
<MobileNav>
|
|
289
|
+
<MobileNavPanel>
|
|
290
|
+
{(activeKey) => <PanelContent activeKey={activeKey} />}
|
|
291
|
+
</MobileNavPanel>
|
|
292
|
+
|
|
293
|
+
<MobileNavBar>
|
|
294
|
+
<MobileNavTrigger value="list" label="Changes">
|
|
295
|
+
<UtilityText className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
296
|
+
</MobileNavTrigger>
|
|
297
|
+
<MobileNavTrigger value="workflow" label="Workflow">
|
|
298
|
+
<UtilityCompare className="size-8 text-gray-500 group-data-active:text-gray-900" />
|
|
299
|
+
</MobileNavTrigger>
|
|
300
|
+
<MobileNavTrigger
|
|
301
|
+
value="edit"
|
|
302
|
+
label="Edits"
|
|
303
|
+
onAction={() => setModalOpen(true)}
|
|
304
|
+
>
|
|
305
|
+
<UtilityRevisionsShow className="size-6 text-gray-500" />
|
|
306
|
+
</MobileNavTrigger>
|
|
307
|
+
<MobileNavTrigger value="comments" label="Comments">
|
|
308
|
+
<UtilityFeedback className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
309
|
+
</MobileNavTrigger>
|
|
310
|
+
</MobileNavBar>
|
|
311
|
+
</MobileNav>
|
|
292
312
|
|
|
293
313
|
{modalOpen && (
|
|
294
|
-
<div className="
|
|
314
|
+
<div className="fixed inset-0 z-60 flex items-center justify-center bg-black/20">
|
|
295
315
|
<div className="relative rounded-xl bg-white p-6 shadow-lg w-72">
|
|
296
316
|
<button
|
|
297
317
|
onClick={() => setModalOpen(false)}
|
|
@@ -307,7 +327,7 @@ const WithCustomActionRender = () => {
|
|
|
307
327
|
</div>
|
|
308
328
|
</div>
|
|
309
329
|
)}
|
|
310
|
-
</
|
|
330
|
+
</div>
|
|
311
331
|
);
|
|
312
332
|
};
|
|
313
333
|
|
|
@@ -320,32 +340,34 @@ export const WithDisabledTrigger: Story = {
|
|
|
320
340
|
args: { children: null },
|
|
321
341
|
|
|
322
342
|
render: () => (
|
|
323
|
-
<
|
|
324
|
-
<div className="
|
|
343
|
+
<div className="h-screen w-full bg-white">
|
|
344
|
+
<div className="p-6">
|
|
325
345
|
<p className="text-sm text-gray-400">
|
|
326
346
|
The “Comments” trigger is disabled because there are no
|
|
327
347
|
comments.
|
|
328
348
|
</p>
|
|
329
349
|
</div>
|
|
330
350
|
|
|
331
|
-
<
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
<
|
|
337
|
-
<
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
<
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
<
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
<
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
351
|
+
<MobileNav>
|
|
352
|
+
<MobileNavPanel>
|
|
353
|
+
{(activeKey) => <PanelContent activeKey={activeKey} />}
|
|
354
|
+
</MobileNavPanel>
|
|
355
|
+
|
|
356
|
+
<MobileNavBar>
|
|
357
|
+
<MobileNavTrigger value="list" label="Changes">
|
|
358
|
+
<UtilityText className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
359
|
+
</MobileNavTrigger>
|
|
360
|
+
<MobileNavTrigger value="workflow" label="Workflow">
|
|
361
|
+
<UtilityCompare className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
362
|
+
</MobileNavTrigger>
|
|
363
|
+
<MobileNavTrigger value="edit" label="Edits">
|
|
364
|
+
<UtilityRevisionsShow className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
365
|
+
</MobileNavTrigger>
|
|
366
|
+
<MobileNavTrigger value="comments" label="Comments" disabled>
|
|
367
|
+
<UtilityFeedback className="size-6 text-gray-500 group-data-active:text-gray-900" />
|
|
368
|
+
</MobileNavTrigger>
|
|
369
|
+
</MobileNavBar>
|
|
370
|
+
</MobileNav>
|
|
371
|
+
</div>
|
|
350
372
|
),
|
|
351
373
|
};
|