@papernote/ui 1.3.1 → 1.5.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/dist/components/BottomNavigation.d.ts +98 -0
- package/dist/components/BottomNavigation.d.ts.map +1 -0
- package/dist/components/Checkbox.d.ts +2 -0
- package/dist/components/Checkbox.d.ts.map +1 -1
- package/dist/components/CheckboxList.d.ts +81 -0
- package/dist/components/CheckboxList.d.ts.map +1 -0
- package/dist/components/Chip.d.ts +92 -1
- package/dist/components/Chip.d.ts.map +1 -1
- package/dist/components/ConfirmDialog.d.ts +43 -1
- package/dist/components/ConfirmDialog.d.ts.map +1 -1
- package/dist/components/DataTable.d.ts +10 -1
- package/dist/components/DataTable.d.ts.map +1 -1
- package/dist/components/DataTableCardView.d.ts +99 -0
- package/dist/components/DataTableCardView.d.ts.map +1 -0
- package/dist/components/ExpandablePanel.d.ts +142 -0
- package/dist/components/ExpandablePanel.d.ts.map +1 -0
- package/dist/components/FloatingActionButton.d.ts +98 -0
- package/dist/components/FloatingActionButton.d.ts.map +1 -0
- package/dist/components/Input.d.ts +45 -1
- package/dist/components/Input.d.ts.map +1 -1
- package/dist/components/MobileHeader.d.ts +98 -0
- package/dist/components/MobileHeader.d.ts.map +1 -0
- package/dist/components/MobileLayout.d.ts +121 -0
- package/dist/components/MobileLayout.d.ts.map +1 -0
- package/dist/components/Modal.d.ts +50 -1
- package/dist/components/Modal.d.ts.map +1 -1
- package/dist/components/PullToRefresh.d.ts +87 -0
- package/dist/components/PullToRefresh.d.ts.map +1 -0
- package/dist/components/QueryTransparency.d.ts +1 -1
- package/dist/components/QueryTransparency.d.ts.map +1 -1
- package/dist/components/SearchableList.d.ts +83 -0
- package/dist/components/SearchableList.d.ts.map +1 -0
- package/dist/components/Select.d.ts +16 -2
- package/dist/components/Select.d.ts.map +1 -1
- package/dist/components/Sidebar.d.ts +40 -1
- package/dist/components/Sidebar.d.ts.map +1 -1
- package/dist/components/SwipeActions.d.ts +93 -0
- package/dist/components/SwipeActions.d.ts.map +1 -0
- package/dist/components/Switch.d.ts +1 -0
- package/dist/components/Switch.d.ts.map +1 -1
- package/dist/components/Textarea.d.ts +13 -0
- package/dist/components/Textarea.d.ts.map +1 -1
- package/dist/components/index.d.ts +27 -3
- package/dist/components/index.d.ts.map +1 -1
- package/dist/context/MobileContext.d.ts +168 -0
- package/dist/context/MobileContext.d.ts.map +1 -0
- package/dist/hooks/useResponsive.d.ts +158 -0
- package/dist/hooks/useResponsive.d.ts.map +1 -0
- package/dist/index.d.ts +1653 -56
- package/dist/index.esm.js +2832 -194
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2865 -192
- package/dist/index.js.map +1 -1
- package/dist/styles.css +404 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/BottomNavigation.stories.tsx +142 -0
- package/src/components/BottomNavigation.tsx +225 -0
- package/src/components/Checkbox.stories.tsx +162 -0
- package/src/components/Checkbox.tsx +22 -6
- package/src/components/CheckboxList.stories.tsx +311 -0
- package/src/components/CheckboxList.tsx +433 -0
- package/src/components/Chip.stories.tsx +389 -0
- package/src/components/Chip.tsx +182 -3
- package/src/components/ConfirmDialog.tsx +56 -4
- package/src/components/DataTable.tsx +60 -1
- package/src/components/DataTableCardView.stories.tsx +307 -0
- package/src/components/DataTableCardView.tsx +419 -0
- package/src/components/ExpandablePanel.stories.tsx +620 -0
- package/src/components/ExpandablePanel.tsx +383 -0
- package/src/components/FloatingActionButton.stories.tsx +197 -0
- package/src/components/FloatingActionButton.tsx +301 -0
- package/src/components/Grid.stories.tsx +16 -16
- package/src/components/Input.stories.tsx +214 -0
- package/src/components/Input.tsx +81 -4
- package/src/components/MobileHeader.stories.tsx +205 -0
- package/src/components/MobileHeader.tsx +233 -0
- package/src/components/MobileLayout.stories.tsx +338 -0
- package/src/components/MobileLayout.tsx +313 -0
- package/src/components/Modal.stories.tsx +183 -0
- package/src/components/Modal.tsx +84 -3
- package/src/components/PullToRefresh.stories.tsx +321 -0
- package/src/components/PullToRefresh.tsx +294 -0
- package/src/components/QueryTransparency.tsx +1 -1
- package/src/components/SearchableList.stories.tsx +437 -0
- package/src/components/SearchableList.tsx +326 -0
- package/src/components/Select.stories.tsx +190 -0
- package/src/components/Select.tsx +353 -137
- package/src/components/Sidebar.tsx +191 -8
- package/src/components/SwipeActions.stories.tsx +327 -0
- package/src/components/SwipeActions.tsx +387 -0
- package/src/components/Switch.stories.tsx +158 -0
- package/src/components/Switch.tsx +12 -3
- package/src/components/Textarea.tsx +31 -1
- package/src/components/index.ts +63 -3
- package/src/context/MobileContext.tsx +296 -0
- package/src/hooks/useResponsive.ts +360 -0
- package/src/types/index.ts +4 -0
- package/tailwind.config.js +56 -1
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import React, { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
import { Loader2, ArrowDown } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* PullToRefresh component props
|
|
6
|
+
*/
|
|
7
|
+
export interface PullToRefreshProps {
|
|
8
|
+
/** Content to wrap */
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
/** Async refresh handler - should return a Promise */
|
|
11
|
+
onRefresh: () => Promise<void>;
|
|
12
|
+
/** Disable pull-to-refresh */
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
/** Pull distance required to trigger refresh (default: 80) */
|
|
15
|
+
pullThreshold?: number;
|
|
16
|
+
/** Maximum pull distance (default: 120) */
|
|
17
|
+
maxPull?: number;
|
|
18
|
+
/** Custom loading indicator */
|
|
19
|
+
loadingIndicator?: React.ReactNode;
|
|
20
|
+
/** Custom pull indicator */
|
|
21
|
+
pullIndicator?: React.ReactNode;
|
|
22
|
+
/** Additional class names for container */
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type RefreshState = 'idle' | 'pulling' | 'ready' | 'refreshing';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* PullToRefresh - Mobile pull-to-refresh gesture handler
|
|
30
|
+
*
|
|
31
|
+
* Wraps content and provides native-feeling pull-to-refresh functionality.
|
|
32
|
+
* Only activates when scrolled to top of content.
|
|
33
|
+
*
|
|
34
|
+
* @example Basic usage
|
|
35
|
+
* ```tsx
|
|
36
|
+
* <PullToRefresh onRefresh={async () => {
|
|
37
|
+
* await fetchLatestData();
|
|
38
|
+
* }}>
|
|
39
|
+
* <div className="min-h-screen">
|
|
40
|
+
* {content}
|
|
41
|
+
* </div>
|
|
42
|
+
* </PullToRefresh>
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example With custom threshold
|
|
46
|
+
* ```tsx
|
|
47
|
+
* <PullToRefresh
|
|
48
|
+
* onRefresh={handleRefresh}
|
|
49
|
+
* pullThreshold={100}
|
|
50
|
+
* maxPull={150}
|
|
51
|
+
* >
|
|
52
|
+
* {content}
|
|
53
|
+
* </PullToRefresh>
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export default function PullToRefresh({
|
|
57
|
+
children,
|
|
58
|
+
onRefresh,
|
|
59
|
+
disabled = false,
|
|
60
|
+
pullThreshold = 80,
|
|
61
|
+
maxPull = 120,
|
|
62
|
+
loadingIndicator,
|
|
63
|
+
pullIndicator,
|
|
64
|
+
className = '',
|
|
65
|
+
}: PullToRefreshProps) {
|
|
66
|
+
const [state, setState] = useState<RefreshState>('idle');
|
|
67
|
+
const [pullDistance, setPullDistance] = useState(0);
|
|
68
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
69
|
+
const startY = useRef(0);
|
|
70
|
+
const currentY = useRef(0);
|
|
71
|
+
|
|
72
|
+
// Check if at top of scroll container
|
|
73
|
+
const isAtTop = useCallback(() => {
|
|
74
|
+
const container = containerRef.current;
|
|
75
|
+
if (!container) return false;
|
|
76
|
+
return container.scrollTop <= 0;
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
// Handle touch start
|
|
80
|
+
const handleTouchStart = useCallback((e: TouchEvent) => {
|
|
81
|
+
if (disabled || state === 'refreshing' || !isAtTop()) return;
|
|
82
|
+
|
|
83
|
+
startY.current = e.touches[0].clientY;
|
|
84
|
+
currentY.current = startY.current;
|
|
85
|
+
}, [disabled, state, isAtTop]);
|
|
86
|
+
|
|
87
|
+
// Handle touch move
|
|
88
|
+
const handleTouchMove = useCallback((e: TouchEvent) => {
|
|
89
|
+
if (disabled || state === 'refreshing') return;
|
|
90
|
+
if (startY.current === 0) return;
|
|
91
|
+
|
|
92
|
+
currentY.current = e.touches[0].clientY;
|
|
93
|
+
const diff = currentY.current - startY.current;
|
|
94
|
+
|
|
95
|
+
// Only allow pulling down when at top
|
|
96
|
+
if (diff > 0 && isAtTop()) {
|
|
97
|
+
// Apply resistance - pull slows down as distance increases
|
|
98
|
+
const resistance = 0.5;
|
|
99
|
+
const adjustedPull = Math.min(diff * resistance, maxPull);
|
|
100
|
+
|
|
101
|
+
setPullDistance(adjustedPull);
|
|
102
|
+
setState(adjustedPull >= pullThreshold ? 'ready' : 'pulling');
|
|
103
|
+
|
|
104
|
+
// Prevent default scroll when pulling
|
|
105
|
+
if (adjustedPull > 0) {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}, [disabled, state, isAtTop, maxPull, pullThreshold]);
|
|
110
|
+
|
|
111
|
+
// Handle touch end
|
|
112
|
+
const handleTouchEnd = useCallback(async () => {
|
|
113
|
+
if (disabled || state === 'refreshing') return;
|
|
114
|
+
|
|
115
|
+
if (state === 'ready') {
|
|
116
|
+
setState('refreshing');
|
|
117
|
+
setPullDistance(pullThreshold); // Hold at threshold while refreshing
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
await onRefresh();
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error('Refresh failed:', error);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
setState('idle');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
setPullDistance(0);
|
|
129
|
+
startY.current = 0;
|
|
130
|
+
currentY.current = 0;
|
|
131
|
+
}, [disabled, state, pullThreshold, onRefresh]);
|
|
132
|
+
|
|
133
|
+
// Attach touch listeners
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
const container = containerRef.current;
|
|
136
|
+
if (!container) return;
|
|
137
|
+
|
|
138
|
+
container.addEventListener('touchstart', handleTouchStart, { passive: true });
|
|
139
|
+
container.addEventListener('touchmove', handleTouchMove, { passive: false });
|
|
140
|
+
container.addEventListener('touchend', handleTouchEnd);
|
|
141
|
+
|
|
142
|
+
return () => {
|
|
143
|
+
container.removeEventListener('touchstart', handleTouchStart);
|
|
144
|
+
container.removeEventListener('touchmove', handleTouchMove);
|
|
145
|
+
container.removeEventListener('touchend', handleTouchEnd);
|
|
146
|
+
};
|
|
147
|
+
}, [handleTouchStart, handleTouchMove, handleTouchEnd]);
|
|
148
|
+
|
|
149
|
+
// Calculate indicator opacity and rotation
|
|
150
|
+
const progress = Math.min(pullDistance / pullThreshold, 1);
|
|
151
|
+
const rotation = progress * 180;
|
|
152
|
+
|
|
153
|
+
// Default loading indicator
|
|
154
|
+
const defaultLoadingIndicator = (
|
|
155
|
+
<Loader2 className="h-6 w-6 text-accent-600 animate-spin" />
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Default pull indicator
|
|
159
|
+
const defaultPullIndicator = (
|
|
160
|
+
<div
|
|
161
|
+
className={`
|
|
162
|
+
transition-transform duration-200
|
|
163
|
+
${state === 'ready' ? 'text-accent-600' : 'text-ink-400'}
|
|
164
|
+
`}
|
|
165
|
+
style={{ transform: `rotate(${rotation}deg)` }}
|
|
166
|
+
>
|
|
167
|
+
<ArrowDown className="h-6 w-6" />
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<div
|
|
173
|
+
ref={containerRef}
|
|
174
|
+
className={`relative overflow-auto ${className}`}
|
|
175
|
+
style={{ touchAction: pullDistance > 0 ? 'none' : 'auto' }}
|
|
176
|
+
>
|
|
177
|
+
{/* Pull indicator */}
|
|
178
|
+
<div
|
|
179
|
+
className={`
|
|
180
|
+
absolute left-0 right-0 flex items-center justify-center
|
|
181
|
+
transition-all duration-200 overflow-hidden
|
|
182
|
+
${state === 'idle' && pullDistance === 0 ? 'opacity-0' : 'opacity-100'}
|
|
183
|
+
`}
|
|
184
|
+
style={{
|
|
185
|
+
height: `${pullDistance}px`,
|
|
186
|
+
top: 0,
|
|
187
|
+
zIndex: 10,
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
190
|
+
<div
|
|
191
|
+
className={`
|
|
192
|
+
w-10 h-10 rounded-full bg-white shadow-md
|
|
193
|
+
flex items-center justify-center
|
|
194
|
+
transition-transform duration-200
|
|
195
|
+
${state === 'refreshing' ? 'scale-100' : progress < 0.3 ? 'scale-75' : 'scale-100'}
|
|
196
|
+
`}
|
|
197
|
+
>
|
|
198
|
+
{state === 'refreshing'
|
|
199
|
+
? (loadingIndicator || defaultLoadingIndicator)
|
|
200
|
+
: (pullIndicator || defaultPullIndicator)
|
|
201
|
+
}
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
{/* Content wrapper */}
|
|
206
|
+
<div
|
|
207
|
+
className="transition-transform duration-200"
|
|
208
|
+
style={{
|
|
209
|
+
transform: `translateY(${pullDistance}px)`,
|
|
210
|
+
}}
|
|
211
|
+
>
|
|
212
|
+
{children}
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* usePullToRefresh - Hook for custom pull-to-refresh implementations
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```tsx
|
|
223
|
+
* const { pullDistance, isRefreshing, bind } = usePullToRefresh({
|
|
224
|
+
* onRefresh: async () => {
|
|
225
|
+
* await fetchData();
|
|
226
|
+
* }
|
|
227
|
+
* });
|
|
228
|
+
*
|
|
229
|
+
* return (
|
|
230
|
+
* <div {...bind}>
|
|
231
|
+
* {isRefreshing && <Spinner />}
|
|
232
|
+
* {content}
|
|
233
|
+
* </div>
|
|
234
|
+
* );
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
export function usePullToRefresh({
|
|
238
|
+
onRefresh,
|
|
239
|
+
pullThreshold = 80,
|
|
240
|
+
maxPull = 120,
|
|
241
|
+
disabled = false,
|
|
242
|
+
}: {
|
|
243
|
+
onRefresh: () => Promise<void>;
|
|
244
|
+
pullThreshold?: number;
|
|
245
|
+
maxPull?: number;
|
|
246
|
+
disabled?: boolean;
|
|
247
|
+
}) {
|
|
248
|
+
const [pullDistance, setPullDistance] = useState(0);
|
|
249
|
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
250
|
+
const startY = useRef(0);
|
|
251
|
+
|
|
252
|
+
const handleTouchStart = useCallback((e: React.TouchEvent) => {
|
|
253
|
+
if (disabled || isRefreshing) return;
|
|
254
|
+
startY.current = e.touches[0].clientY;
|
|
255
|
+
}, [disabled, isRefreshing]);
|
|
256
|
+
|
|
257
|
+
const handleTouchMove = useCallback((e: React.TouchEvent) => {
|
|
258
|
+
if (disabled || isRefreshing || startY.current === 0) return;
|
|
259
|
+
|
|
260
|
+
const diff = e.touches[0].clientY - startY.current;
|
|
261
|
+
if (diff > 0) {
|
|
262
|
+
const adjustedPull = Math.min(diff * 0.5, maxPull);
|
|
263
|
+
setPullDistance(adjustedPull);
|
|
264
|
+
}
|
|
265
|
+
}, [disabled, isRefreshing, maxPull]);
|
|
266
|
+
|
|
267
|
+
const handleTouchEnd = useCallback(async () => {
|
|
268
|
+
if (disabled || isRefreshing) return;
|
|
269
|
+
|
|
270
|
+
if (pullDistance >= pullThreshold) {
|
|
271
|
+
setIsRefreshing(true);
|
|
272
|
+
try {
|
|
273
|
+
await onRefresh();
|
|
274
|
+
} finally {
|
|
275
|
+
setIsRefreshing(false);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
setPullDistance(0);
|
|
280
|
+
startY.current = 0;
|
|
281
|
+
}, [disabled, isRefreshing, pullDistance, pullThreshold, onRefresh]);
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
pullDistance,
|
|
285
|
+
isRefreshing,
|
|
286
|
+
isReady: pullDistance >= pullThreshold,
|
|
287
|
+
progress: Math.min(pullDistance / pullThreshold, 1),
|
|
288
|
+
bind: {
|
|
289
|
+
onTouchStart: handleTouchStart,
|
|
290
|
+
onTouchMove: handleTouchMove,
|
|
291
|
+
onTouchEnd: handleTouchEnd,
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
}
|
|
@@ -37,7 +37,7 @@ export interface QueryTransparencyInfo {
|
|
|
37
37
|
relatedData?: Array<{
|
|
38
38
|
entity: string;
|
|
39
39
|
description: string;
|
|
40
|
-
type: 'join' | 'include' | 'lookup';
|
|
40
|
+
type: 'primary' | 'join' | 'include' | 'lookup';
|
|
41
41
|
}>;
|
|
42
42
|
// Backend calculations
|
|
43
43
|
calculations?: Array<{
|