@papernote/ui 1.7.7 → 1.8.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/Badge.d.ts +3 -1
- package/dist/components/Badge.d.ts.map +1 -1
- package/dist/components/BottomSheet.d.ts +72 -8
- package/dist/components/BottomSheet.d.ts.map +1 -1
- package/dist/components/CompactStat.d.ts +52 -0
- package/dist/components/CompactStat.d.ts.map +1 -0
- package/dist/components/HorizontalScroll.d.ts +43 -0
- package/dist/components/HorizontalScroll.d.ts.map +1 -0
- package/dist/components/NotificationBanner.d.ts +53 -0
- package/dist/components/NotificationBanner.d.ts.map +1 -0
- package/dist/components/Progress.d.ts +2 -2
- package/dist/components/Progress.d.ts.map +1 -1
- package/dist/components/PullToRefresh.d.ts +23 -71
- package/dist/components/PullToRefresh.d.ts.map +1 -1
- package/dist/components/Stack.d.ts +2 -1
- package/dist/components/Stack.d.ts.map +1 -1
- package/dist/components/SwipeableCard.d.ts +65 -0
- package/dist/components/SwipeableCard.d.ts.map +1 -0
- package/dist/components/Text.d.ts +9 -2
- package/dist/components/Text.d.ts.map +1 -1
- package/dist/components/index.d.ts +11 -3
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.d.ts +317 -86
- package/dist/index.esm.js +932 -253
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +937 -252
- package/dist/index.js.map +1 -1
- package/dist/styles.css +178 -8
- package/package.json +1 -1
- package/src/components/Badge.tsx +13 -2
- package/src/components/BottomSheet.tsx +227 -98
- package/src/components/Card.tsx +1 -1
- package/src/components/CompactStat.tsx +150 -0
- package/src/components/HorizontalScroll.tsx +275 -0
- package/src/components/NotificationBanner.tsx +238 -0
- package/src/components/Progress.tsx +6 -3
- package/src/components/PullToRefresh.tsx +158 -196
- package/src/components/Stack.tsx +4 -1
- package/src/components/SwipeableCard.tsx +347 -0
- package/src/components/Text.tsx +45 -3
- package/src/components/index.ts +16 -3
- package/src/styles/index.css +32 -0
|
@@ -1,136 +1,155 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import { Loader2, ArrowDown } from 'lucide-react';
|
|
1
|
+
import React, { useRef, useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import { Loader2, ArrowDown, Check } from 'lucide-react';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* PullToRefresh component props
|
|
6
|
-
*/
|
|
7
4
|
export interface PullToRefreshProps {
|
|
8
|
-
/** Content to wrap */
|
|
5
|
+
/** Content to wrap with pull-to-refresh functionality */
|
|
9
6
|
children: React.ReactNode;
|
|
10
|
-
/** Async refresh
|
|
7
|
+
/** Async callback when refresh is triggered - should return when refresh is complete */
|
|
11
8
|
onRefresh: () => Promise<void>;
|
|
9
|
+
/** Pixels to pull before triggering refresh */
|
|
10
|
+
threshold?: number;
|
|
12
11
|
/** Disable pull-to-refresh */
|
|
13
12
|
disabled?: boolean;
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
/** Custom
|
|
19
|
-
|
|
20
|
-
/** Custom
|
|
21
|
-
|
|
22
|
-
/** Additional class
|
|
13
|
+
/** Custom content shown while pulling */
|
|
14
|
+
pullingContent?: React.ReactNode;
|
|
15
|
+
/** Custom content shown when ready to release */
|
|
16
|
+
releaseContent?: React.ReactNode;
|
|
17
|
+
/** Custom content shown while refreshing */
|
|
18
|
+
refreshingContent?: React.ReactNode;
|
|
19
|
+
/** Custom content shown when refresh completes (briefly) */
|
|
20
|
+
completeContent?: React.ReactNode;
|
|
21
|
+
/** Additional class name for container */
|
|
23
22
|
className?: string;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
type RefreshState = 'idle' | 'pulling' | 'ready' | 'refreshing';
|
|
25
|
+
type RefreshState = 'idle' | 'pulling' | 'ready' | 'refreshing' | 'complete';
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
|
-
* PullToRefresh -
|
|
30
|
-
*
|
|
31
|
-
* Wraps content
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* await fetchLatestData();
|
|
38
|
-
* }}>
|
|
39
|
-
* <div className="min-h-screen">
|
|
40
|
-
* {content}
|
|
41
|
-
* </div>
|
|
42
|
-
* </PullToRefresh>
|
|
43
|
-
* ```
|
|
44
|
-
*
|
|
45
|
-
* @example With custom threshold
|
|
28
|
+
* PullToRefresh - Pull-down refresh indicator and handler for mobile lists
|
|
29
|
+
*
|
|
30
|
+
* Wraps content to enable pull-to-refresh behavior on mobile:
|
|
31
|
+
* - Pull down to trigger refresh
|
|
32
|
+
* - Visual feedback showing progress
|
|
33
|
+
* - Custom content for each state
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
46
36
|
* ```tsx
|
|
47
|
-
* <PullToRefresh
|
|
48
|
-
*
|
|
49
|
-
* pullThreshold={100}
|
|
50
|
-
* maxPull={150}
|
|
51
|
-
* >
|
|
52
|
-
* {content}
|
|
37
|
+
* <PullToRefresh onRefresh={async () => { await syncData(); }}>
|
|
38
|
+
* <TransactionList transactions={transactions} />
|
|
53
39
|
* </PullToRefresh>
|
|
54
40
|
* ```
|
|
55
41
|
*/
|
|
56
|
-
export
|
|
42
|
+
export function PullToRefresh({
|
|
57
43
|
children,
|
|
58
44
|
onRefresh,
|
|
45
|
+
threshold = 80,
|
|
59
46
|
disabled = false,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
47
|
+
pullingContent,
|
|
48
|
+
releaseContent,
|
|
49
|
+
refreshingContent,
|
|
50
|
+
completeContent,
|
|
64
51
|
className = '',
|
|
65
52
|
}: PullToRefreshProps) {
|
|
53
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
66
54
|
const [state, setState] = useState<RefreshState>('idle');
|
|
67
55
|
const [pullDistance, setPullDistance] = useState(0);
|
|
68
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
69
56
|
const startY = useRef(0);
|
|
70
57
|
const currentY = useRef(0);
|
|
58
|
+
const isDragging = useRef(false);
|
|
71
59
|
|
|
72
|
-
// Check if at top
|
|
60
|
+
// Check if content is at top (can pull to refresh)
|
|
73
61
|
const isAtTop = useCallback(() => {
|
|
74
62
|
const container = containerRef.current;
|
|
75
63
|
if (!container) return false;
|
|
76
|
-
|
|
64
|
+
|
|
65
|
+
// Check if the scrollable content is at the top
|
|
66
|
+
const scrollableParent = container.querySelector('[data-ptr-scrollable]') || container;
|
|
67
|
+
return (scrollableParent as HTMLElement).scrollTop <= 0;
|
|
77
68
|
}, []);
|
|
78
69
|
|
|
79
|
-
// Handle
|
|
70
|
+
// Handle pull start
|
|
80
71
|
const handleTouchStart = useCallback((e: TouchEvent) => {
|
|
81
|
-
if (disabled || state === 'refreshing'
|
|
82
|
-
|
|
72
|
+
if (disabled || state === 'refreshing') return;
|
|
73
|
+
if (!isAtTop()) return;
|
|
74
|
+
|
|
75
|
+
isDragging.current = true;
|
|
83
76
|
startY.current = e.touches[0].clientY;
|
|
84
|
-
currentY.current =
|
|
77
|
+
currentY.current = e.touches[0].clientY;
|
|
85
78
|
}, [disabled, state, isAtTop]);
|
|
86
79
|
|
|
87
|
-
// Handle
|
|
80
|
+
// Handle pull move
|
|
88
81
|
const handleTouchMove = useCallback((e: TouchEvent) => {
|
|
89
|
-
if (disabled || state === 'refreshing') return;
|
|
90
|
-
if (startY.current === 0) return;
|
|
82
|
+
if (!isDragging.current || disabled || state === 'refreshing') return;
|
|
91
83
|
|
|
92
84
|
currentY.current = e.touches[0].clientY;
|
|
93
|
-
const
|
|
85
|
+
const delta = currentY.current - startY.current;
|
|
86
|
+
|
|
87
|
+
// Only activate pull-to-refresh when pulling down
|
|
88
|
+
if (delta < 0) {
|
|
89
|
+
isDragging.current = false;
|
|
90
|
+
setPullDistance(0);
|
|
91
|
+
setState('idle');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check if we're at the top before allowing pull
|
|
96
|
+
if (!isAtTop()) {
|
|
97
|
+
isDragging.current = false;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
94
100
|
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
setPullDistance(adjustedPull);
|
|
102
|
-
setState(adjustedPull >= pullThreshold ? 'ready' : 'pulling');
|
|
101
|
+
// Apply resistance to pull
|
|
102
|
+
const resistance = 0.5;
|
|
103
|
+
const resistedDelta = delta * resistance;
|
|
104
|
+
const maxPull = threshold * 2;
|
|
105
|
+
const clampedDelta = Math.min(resistedDelta, maxPull);
|
|
103
106
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
setPullDistance(clampedDelta);
|
|
108
|
+
|
|
109
|
+
// Update state based on pull distance
|
|
110
|
+
if (clampedDelta >= threshold) {
|
|
111
|
+
setState('ready');
|
|
112
|
+
} else if (clampedDelta > 0) {
|
|
113
|
+
setState('pulling');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Prevent default scroll when pulling
|
|
117
|
+
if (delta > 0 && isAtTop()) {
|
|
118
|
+
e.preventDefault();
|
|
108
119
|
}
|
|
109
|
-
}, [disabled, state,
|
|
120
|
+
}, [disabled, state, threshold, isAtTop]);
|
|
110
121
|
|
|
111
|
-
// Handle
|
|
122
|
+
// Handle pull end
|
|
112
123
|
const handleTouchEnd = useCallback(async () => {
|
|
113
|
-
if (
|
|
124
|
+
if (!isDragging.current) return;
|
|
125
|
+
isDragging.current = false;
|
|
114
126
|
|
|
115
|
-
if (state === 'ready') {
|
|
127
|
+
if (state === 'ready' && pullDistance >= threshold) {
|
|
116
128
|
setState('refreshing');
|
|
117
|
-
setPullDistance(
|
|
129
|
+
setPullDistance(threshold * 0.6); // Settle at a smaller height while refreshing
|
|
118
130
|
|
|
119
131
|
try {
|
|
120
132
|
await onRefresh();
|
|
133
|
+
setState('complete');
|
|
134
|
+
|
|
135
|
+
// Show complete state briefly
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
setState('idle');
|
|
138
|
+
setPullDistance(0);
|
|
139
|
+
}, 500);
|
|
121
140
|
} catch (error) {
|
|
122
141
|
console.error('Refresh failed:', error);
|
|
142
|
+
setState('idle');
|
|
143
|
+
setPullDistance(0);
|
|
123
144
|
}
|
|
124
|
-
|
|
145
|
+
} else {
|
|
146
|
+
// Snap back
|
|
125
147
|
setState('idle');
|
|
148
|
+
setPullDistance(0);
|
|
126
149
|
}
|
|
150
|
+
}, [state, pullDistance, threshold, onRefresh]);
|
|
127
151
|
|
|
128
|
-
|
|
129
|
-
startY.current = 0;
|
|
130
|
-
currentY.current = 0;
|
|
131
|
-
}, [disabled, state, pullThreshold, onRefresh]);
|
|
132
|
-
|
|
133
|
-
// Attach touch listeners
|
|
152
|
+
// Attach touch event listeners
|
|
134
153
|
useEffect(() => {
|
|
135
154
|
const container = containerRef.current;
|
|
136
155
|
if (!container) return;
|
|
@@ -146,65 +165,84 @@ export default function PullToRefresh({
|
|
|
146
165
|
};
|
|
147
166
|
}, [handleTouchStart, handleTouchMove, handleTouchEnd]);
|
|
148
167
|
|
|
149
|
-
// Calculate
|
|
150
|
-
const progress = Math.min(pullDistance /
|
|
151
|
-
const rotation = progress * 180;
|
|
168
|
+
// Calculate progress percentage
|
|
169
|
+
const progress = Math.min(1, pullDistance / threshold);
|
|
152
170
|
|
|
153
|
-
// Default
|
|
154
|
-
const
|
|
155
|
-
<
|
|
171
|
+
// Default content for each state
|
|
172
|
+
const defaultPullingContent = (
|
|
173
|
+
<div className="flex flex-col items-center gap-1">
|
|
174
|
+
<ArrowDown
|
|
175
|
+
className="h-5 w-5 text-ink-400 transition-transform duration-200"
|
|
176
|
+
style={{ transform: `rotate(${progress * 180}deg)` }}
|
|
177
|
+
/>
|
|
178
|
+
<span className="text-xs text-ink-500">Pull to refresh</span>
|
|
179
|
+
</div>
|
|
156
180
|
);
|
|
157
181
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
182
|
+
const defaultReleaseContent = (
|
|
183
|
+
<div className="flex flex-col items-center gap-1">
|
|
184
|
+
<ArrowDown
|
|
185
|
+
className="h-5 w-5 text-accent-500 rotate-180"
|
|
186
|
+
/>
|
|
187
|
+
<span className="text-xs text-accent-600 font-medium">Release to refresh</span>
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const defaultRefreshingContent = (
|
|
192
|
+
<div className="flex flex-col items-center gap-1">
|
|
193
|
+
<Loader2 className="h-5 w-5 text-accent-500 animate-spin" />
|
|
194
|
+
<span className="text-xs text-ink-500">Refreshing...</span>
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const defaultCompleteContent = (
|
|
199
|
+
<div className="flex flex-col items-center gap-1">
|
|
200
|
+
<Check className="h-5 w-5 text-success-500" />
|
|
201
|
+
<span className="text-xs text-success-600">Done!</span>
|
|
168
202
|
</div>
|
|
169
203
|
);
|
|
170
204
|
|
|
205
|
+
// Get content based on current state
|
|
206
|
+
const getIndicatorContent = () => {
|
|
207
|
+
switch (state) {
|
|
208
|
+
case 'pulling':
|
|
209
|
+
return pullingContent || defaultPullingContent;
|
|
210
|
+
case 'ready':
|
|
211
|
+
return releaseContent || defaultReleaseContent;
|
|
212
|
+
case 'refreshing':
|
|
213
|
+
return refreshingContent || defaultRefreshingContent;
|
|
214
|
+
case 'complete':
|
|
215
|
+
return completeContent || defaultCompleteContent;
|
|
216
|
+
default:
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
171
221
|
return (
|
|
172
|
-
<div
|
|
222
|
+
<div
|
|
173
223
|
ref={containerRef}
|
|
174
|
-
className={`relative overflow-
|
|
175
|
-
style={{ touchAction: pullDistance > 0 ? 'none' : 'auto' }}
|
|
224
|
+
className={`relative overflow-hidden ${className}`}
|
|
176
225
|
>
|
|
177
226
|
{/* Pull indicator */}
|
|
178
227
|
<div
|
|
179
228
|
className={`
|
|
180
|
-
absolute left-0 right-0
|
|
181
|
-
|
|
182
|
-
|
|
229
|
+
absolute top-0 left-0 right-0
|
|
230
|
+
flex items-center justify-center
|
|
231
|
+
bg-paper-50
|
|
232
|
+
transition-all duration-200 ease-out
|
|
233
|
+
${state === 'idle' ? 'opacity-0' : 'opacity-100'}
|
|
183
234
|
`}
|
|
184
235
|
style={{
|
|
185
|
-
height:
|
|
186
|
-
|
|
187
|
-
zIndex: 10,
|
|
236
|
+
height: pullDistance,
|
|
237
|
+
transform: state === 'idle' ? 'translateY(-100%)' : 'translateY(0)',
|
|
188
238
|
}}
|
|
189
239
|
>
|
|
190
|
-
|
|
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>
|
|
240
|
+
{getIndicatorContent()}
|
|
203
241
|
</div>
|
|
204
242
|
|
|
205
243
|
{/* Content wrapper */}
|
|
206
244
|
<div
|
|
207
|
-
className="transition-transform duration-200"
|
|
245
|
+
className="transition-transform duration-200 ease-out"
|
|
208
246
|
style={{
|
|
209
247
|
transform: `translateY(${pullDistance}px)`,
|
|
210
248
|
}}
|
|
@@ -215,80 +253,4 @@ export default function PullToRefresh({
|
|
|
215
253
|
);
|
|
216
254
|
}
|
|
217
255
|
|
|
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
|
-
}
|
|
256
|
+
export default PullToRefresh;
|
package/src/components/Stack.tsx
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import React, { forwardRef } from 'react';
|
|
5
5
|
|
|
6
|
-
type SpacingValue = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
6
|
+
type SpacingValue = 'none' | 'tight' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
7
7
|
|
|
8
8
|
export interface StackProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
9
9
|
/** Content to stack */
|
|
@@ -31,6 +31,7 @@ export interface StackProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
31
31
|
*
|
|
32
32
|
* Spacing scale (use either `spacing` or `gap` prop - they're aliases):
|
|
33
33
|
* - none: 0
|
|
34
|
+
* - tight: 0.25rem (1) - for mobile-density layouts
|
|
34
35
|
* - xs: 0.5rem (2)
|
|
35
36
|
* - sm: 0.75rem (3)
|
|
36
37
|
* - md: 1.5rem (6)
|
|
@@ -69,6 +70,7 @@ export const Stack = forwardRef<HTMLDivElement, StackProps>(({
|
|
|
69
70
|
const spacingClasses = {
|
|
70
71
|
vertical: {
|
|
71
72
|
none: '',
|
|
73
|
+
tight: 'space-y-1', // 4px - for mobile-density layouts
|
|
72
74
|
xs: 'space-y-2',
|
|
73
75
|
sm: 'space-y-3',
|
|
74
76
|
md: 'space-y-6',
|
|
@@ -77,6 +79,7 @@ export const Stack = forwardRef<HTMLDivElement, StackProps>(({
|
|
|
77
79
|
},
|
|
78
80
|
horizontal: {
|
|
79
81
|
none: '',
|
|
82
|
+
tight: 'space-x-1', // 4px - for mobile-density layouts
|
|
80
83
|
xs: 'space-x-2',
|
|
81
84
|
sm: 'space-x-3',
|
|
82
85
|
md: 'space-x-6',
|