@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
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import React, { useRef, useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import { Check, MoreHorizontal } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
export interface SwipeAction {
|
|
5
|
+
/** Icon to display in action area */
|
|
6
|
+
icon: React.ReactNode;
|
|
7
|
+
/** Background color variant */
|
|
8
|
+
color: 'success' | 'error' | 'warning' | 'neutral' | 'primary';
|
|
9
|
+
/** Label for accessibility */
|
|
10
|
+
label: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SwipeableCardProps {
|
|
14
|
+
/** Card content */
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
/** Handler called when swiped right past threshold */
|
|
17
|
+
onSwipeRight?: () => void;
|
|
18
|
+
/** Handler called when swiped left past threshold */
|
|
19
|
+
onSwipeLeft?: () => void;
|
|
20
|
+
/** Right swipe action configuration */
|
|
21
|
+
rightAction?: SwipeAction;
|
|
22
|
+
/** Left swipe action configuration */
|
|
23
|
+
leftAction?: SwipeAction;
|
|
24
|
+
/** Pixels of swipe before action triggers */
|
|
25
|
+
swipeThreshold?: number;
|
|
26
|
+
/** Enable haptic feedback on mobile (if supported) */
|
|
27
|
+
hapticFeedback?: boolean;
|
|
28
|
+
/** Disable swipe interactions */
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
/** Callback when swipe starts */
|
|
31
|
+
onSwipeStart?: () => void;
|
|
32
|
+
/** Callback when swipe ends (regardless of trigger) */
|
|
33
|
+
onSwipeEnd?: () => void;
|
|
34
|
+
/** Additional class name */
|
|
35
|
+
className?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* SwipeableCard - Card component with swipe-to-action functionality
|
|
40
|
+
*
|
|
41
|
+
* Designed for mobile approval workflows:
|
|
42
|
+
* - Swipe right to approve/confirm
|
|
43
|
+
* - Swipe left to see options/alternatives
|
|
44
|
+
* - Visual feedback showing action being revealed
|
|
45
|
+
* - Haptic feedback on mobile devices
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* <SwipeableCard
|
|
50
|
+
* onSwipeRight={() => handleApprove()}
|
|
51
|
+
* onSwipeLeft={() => handleShowOptions()}
|
|
52
|
+
* rightAction={{
|
|
53
|
+
* icon: <Check />,
|
|
54
|
+
* color: 'success',
|
|
55
|
+
* label: 'Approve'
|
|
56
|
+
* }}
|
|
57
|
+
* leftAction={{
|
|
58
|
+
* icon: <MoreHorizontal />,
|
|
59
|
+
* color: 'neutral',
|
|
60
|
+
* label: 'Options'
|
|
61
|
+
* }}
|
|
62
|
+
* >
|
|
63
|
+
* <TransactionContent />
|
|
64
|
+
* </SwipeableCard>
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export function SwipeableCard({
|
|
68
|
+
children,
|
|
69
|
+
onSwipeRight,
|
|
70
|
+
onSwipeLeft,
|
|
71
|
+
rightAction = {
|
|
72
|
+
icon: <Check className="h-6 w-6" />,
|
|
73
|
+
color: 'success',
|
|
74
|
+
label: 'Approve',
|
|
75
|
+
},
|
|
76
|
+
leftAction = {
|
|
77
|
+
icon: <MoreHorizontal className="h-6 w-6" />,
|
|
78
|
+
color: 'neutral',
|
|
79
|
+
label: 'Options',
|
|
80
|
+
},
|
|
81
|
+
swipeThreshold = 100,
|
|
82
|
+
hapticFeedback = true,
|
|
83
|
+
disabled = false,
|
|
84
|
+
onSwipeStart,
|
|
85
|
+
onSwipeEnd,
|
|
86
|
+
className = '',
|
|
87
|
+
}: SwipeableCardProps) {
|
|
88
|
+
const cardRef = useRef<HTMLDivElement>(null);
|
|
89
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
90
|
+
const [offsetX, setOffsetX] = useState(0);
|
|
91
|
+
const [isTriggered, setIsTriggered] = useState<'left' | 'right' | null>(null);
|
|
92
|
+
const startX = useRef(0);
|
|
93
|
+
const startY = useRef(0);
|
|
94
|
+
const isHorizontalSwipe = useRef<boolean | null>(null);
|
|
95
|
+
|
|
96
|
+
// Color classes for action backgrounds
|
|
97
|
+
const colorClasses: Record<SwipeAction['color'], string> = {
|
|
98
|
+
success: 'bg-success-500',
|
|
99
|
+
error: 'bg-error-500',
|
|
100
|
+
warning: 'bg-warning-500',
|
|
101
|
+
neutral: 'bg-paper-400',
|
|
102
|
+
primary: 'bg-accent-500',
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Trigger haptic feedback
|
|
106
|
+
const triggerHaptic = useCallback((style: 'light' | 'medium' | 'heavy' = 'medium') => {
|
|
107
|
+
if (!hapticFeedback) return;
|
|
108
|
+
|
|
109
|
+
// Use Vibration API if available
|
|
110
|
+
if ('vibrate' in navigator) {
|
|
111
|
+
const patterns: Record<string, number | number[]> = {
|
|
112
|
+
light: 10,
|
|
113
|
+
medium: 25,
|
|
114
|
+
heavy: [50, 30, 50],
|
|
115
|
+
};
|
|
116
|
+
navigator.vibrate(patterns[style]);
|
|
117
|
+
}
|
|
118
|
+
}, [hapticFeedback]);
|
|
119
|
+
|
|
120
|
+
// Handle drag start
|
|
121
|
+
const handleDragStart = useCallback((clientX: number, clientY: number) => {
|
|
122
|
+
if (disabled) return;
|
|
123
|
+
|
|
124
|
+
setIsDragging(true);
|
|
125
|
+
startX.current = clientX;
|
|
126
|
+
startY.current = clientY;
|
|
127
|
+
isHorizontalSwipe.current = null;
|
|
128
|
+
onSwipeStart?.();
|
|
129
|
+
}, [disabled, onSwipeStart]);
|
|
130
|
+
|
|
131
|
+
// Handle drag move
|
|
132
|
+
const handleDragMove = useCallback((clientX: number, clientY: number) => {
|
|
133
|
+
if (!isDragging || disabled) return;
|
|
134
|
+
|
|
135
|
+
const deltaX = clientX - startX.current;
|
|
136
|
+
const deltaY = clientY - startY.current;
|
|
137
|
+
|
|
138
|
+
// Determine if this is a horizontal swipe on first significant movement
|
|
139
|
+
if (isHorizontalSwipe.current === null) {
|
|
140
|
+
const absDeltaX = Math.abs(deltaX);
|
|
141
|
+
const absDeltaY = Math.abs(deltaY);
|
|
142
|
+
|
|
143
|
+
if (absDeltaX > 10 || absDeltaY > 10) {
|
|
144
|
+
isHorizontalSwipe.current = absDeltaX > absDeltaY;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Only process horizontal swipes
|
|
149
|
+
if (isHorizontalSwipe.current !== true) return;
|
|
150
|
+
|
|
151
|
+
// Check if we should allow this direction
|
|
152
|
+
const canSwipeRight = onSwipeRight !== undefined;
|
|
153
|
+
const canSwipeLeft = onSwipeLeft !== undefined;
|
|
154
|
+
|
|
155
|
+
let newOffset = deltaX;
|
|
156
|
+
|
|
157
|
+
// Limit swipe direction based on available actions
|
|
158
|
+
if (!canSwipeRight && deltaX > 0) newOffset = 0;
|
|
159
|
+
if (!canSwipeLeft && deltaX < 0) newOffset = 0;
|
|
160
|
+
|
|
161
|
+
// Add resistance when exceeding threshold
|
|
162
|
+
const maxSwipe = swipeThreshold * 1.5;
|
|
163
|
+
if (Math.abs(newOffset) > swipeThreshold) {
|
|
164
|
+
const overflow = Math.abs(newOffset) - swipeThreshold;
|
|
165
|
+
const resistance = overflow * 0.3;
|
|
166
|
+
newOffset = newOffset > 0
|
|
167
|
+
? swipeThreshold + resistance
|
|
168
|
+
: -(swipeThreshold + resistance);
|
|
169
|
+
newOffset = Math.max(-maxSwipe, Math.min(maxSwipe, newOffset));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
setOffsetX(newOffset);
|
|
173
|
+
|
|
174
|
+
// Check for threshold crossing and trigger haptic
|
|
175
|
+
const newTriggered = Math.abs(newOffset) >= swipeThreshold
|
|
176
|
+
? (newOffset > 0 ? 'right' : 'left')
|
|
177
|
+
: null;
|
|
178
|
+
|
|
179
|
+
if (newTriggered !== isTriggered) {
|
|
180
|
+
if (newTriggered) {
|
|
181
|
+
triggerHaptic('medium');
|
|
182
|
+
}
|
|
183
|
+
setIsTriggered(newTriggered);
|
|
184
|
+
}
|
|
185
|
+
}, [isDragging, disabled, onSwipeRight, onSwipeLeft, swipeThreshold, isTriggered, triggerHaptic]);
|
|
186
|
+
|
|
187
|
+
// Handle drag end
|
|
188
|
+
const handleDragEnd = useCallback(() => {
|
|
189
|
+
if (!isDragging) return;
|
|
190
|
+
|
|
191
|
+
setIsDragging(false);
|
|
192
|
+
onSwipeEnd?.();
|
|
193
|
+
|
|
194
|
+
// Check if action should be triggered
|
|
195
|
+
if (Math.abs(offsetX) >= swipeThreshold) {
|
|
196
|
+
if (offsetX > 0 && onSwipeRight) {
|
|
197
|
+
triggerHaptic('heavy');
|
|
198
|
+
// Animate card away then call handler
|
|
199
|
+
setOffsetX(window.innerWidth);
|
|
200
|
+
setTimeout(() => {
|
|
201
|
+
onSwipeRight();
|
|
202
|
+
setOffsetX(0);
|
|
203
|
+
setIsTriggered(null);
|
|
204
|
+
}, 200);
|
|
205
|
+
return;
|
|
206
|
+
} else if (offsetX < 0 && onSwipeLeft) {
|
|
207
|
+
triggerHaptic('heavy');
|
|
208
|
+
setOffsetX(-window.innerWidth);
|
|
209
|
+
setTimeout(() => {
|
|
210
|
+
onSwipeLeft();
|
|
211
|
+
setOffsetX(0);
|
|
212
|
+
setIsTriggered(null);
|
|
213
|
+
}, 200);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Snap back
|
|
219
|
+
setOffsetX(0);
|
|
220
|
+
setIsTriggered(null);
|
|
221
|
+
}, [isDragging, offsetX, swipeThreshold, onSwipeRight, onSwipeLeft, onSwipeEnd, triggerHaptic]);
|
|
222
|
+
|
|
223
|
+
// Touch event handlers
|
|
224
|
+
const handleTouchStart = (e: React.TouchEvent) => {
|
|
225
|
+
handleDragStart(e.touches[0].clientX, e.touches[0].clientY);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const handleTouchMove = (e: React.TouchEvent) => {
|
|
229
|
+
handleDragMove(e.touches[0].clientX, e.touches[0].clientY);
|
|
230
|
+
|
|
231
|
+
// Prevent vertical scroll if horizontal swipe
|
|
232
|
+
if (isHorizontalSwipe.current === true) {
|
|
233
|
+
e.preventDefault();
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const handleTouchEnd = () => {
|
|
238
|
+
handleDragEnd();
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// Mouse event handlers (for desktop testing)
|
|
242
|
+
const handleMouseDown = (e: React.MouseEvent) => {
|
|
243
|
+
handleDragStart(e.clientX, e.clientY);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
if (!isDragging) return;
|
|
248
|
+
|
|
249
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
250
|
+
handleDragMove(e.clientX, e.clientY);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const handleMouseUp = () => {
|
|
254
|
+
handleDragEnd();
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
258
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
259
|
+
|
|
260
|
+
return () => {
|
|
261
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
262
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
263
|
+
};
|
|
264
|
+
}, [isDragging, handleDragMove, handleDragEnd]);
|
|
265
|
+
|
|
266
|
+
// Calculate action opacity based on swipe distance
|
|
267
|
+
const rightActionOpacity = offsetX > 0 ? Math.min(1, offsetX / swipeThreshold) : 0;
|
|
268
|
+
const leftActionOpacity = offsetX < 0 ? Math.min(1, Math.abs(offsetX) / swipeThreshold) : 0;
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<div className={`relative overflow-hidden rounded-lg ${className}`}>
|
|
272
|
+
{/* Right action background (revealed when swiping right) */}
|
|
273
|
+
{onSwipeRight && (
|
|
274
|
+
<div
|
|
275
|
+
className={`
|
|
276
|
+
absolute inset-y-0 left-0 flex items-center justify-start pl-6
|
|
277
|
+
${colorClasses[rightAction.color]}
|
|
278
|
+
transition-opacity duration-100
|
|
279
|
+
`}
|
|
280
|
+
style={{
|
|
281
|
+
opacity: rightActionOpacity,
|
|
282
|
+
width: Math.abs(offsetX) + 20,
|
|
283
|
+
}}
|
|
284
|
+
aria-hidden="true"
|
|
285
|
+
>
|
|
286
|
+
<div
|
|
287
|
+
className={`
|
|
288
|
+
text-white transform transition-transform duration-200
|
|
289
|
+
${isTriggered === 'right' ? 'scale-125' : 'scale-100'}
|
|
290
|
+
`}
|
|
291
|
+
>
|
|
292
|
+
{rightAction.icon}
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
)}
|
|
296
|
+
|
|
297
|
+
{/* Left action background (revealed when swiping left) */}
|
|
298
|
+
{onSwipeLeft && (
|
|
299
|
+
<div
|
|
300
|
+
className={`
|
|
301
|
+
absolute inset-y-0 right-0 flex items-center justify-end pr-6
|
|
302
|
+
${colorClasses[leftAction.color]}
|
|
303
|
+
transition-opacity duration-100
|
|
304
|
+
`}
|
|
305
|
+
style={{
|
|
306
|
+
opacity: leftActionOpacity,
|
|
307
|
+
width: Math.abs(offsetX) + 20,
|
|
308
|
+
}}
|
|
309
|
+
aria-hidden="true"
|
|
310
|
+
>
|
|
311
|
+
<div
|
|
312
|
+
className={`
|
|
313
|
+
text-white transform transition-transform duration-200
|
|
314
|
+
${isTriggered === 'left' ? 'scale-125' : 'scale-100'}
|
|
315
|
+
`}
|
|
316
|
+
>
|
|
317
|
+
{leftAction.icon}
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
)}
|
|
321
|
+
|
|
322
|
+
{/* Card content */}
|
|
323
|
+
<div
|
|
324
|
+
ref={cardRef}
|
|
325
|
+
className={`
|
|
326
|
+
relative bg-white
|
|
327
|
+
${isDragging ? '' : 'transition-transform duration-200 ease-out'}
|
|
328
|
+
${disabled ? 'opacity-50 pointer-events-none' : ''}
|
|
329
|
+
`}
|
|
330
|
+
style={{
|
|
331
|
+
transform: `translateX(${offsetX}px)`,
|
|
332
|
+
}}
|
|
333
|
+
onTouchStart={handleTouchStart}
|
|
334
|
+
onTouchMove={handleTouchMove}
|
|
335
|
+
onTouchEnd={handleTouchEnd}
|
|
336
|
+
onMouseDown={handleMouseDown}
|
|
337
|
+
role="button"
|
|
338
|
+
aria-label={`Swipeable card. ${onSwipeRight ? `Swipe right to ${rightAction.label}.` : ''} ${onSwipeLeft ? `Swipe left to ${leftAction.label}.` : ''}`}
|
|
339
|
+
tabIndex={disabled ? -1 : 0}
|
|
340
|
+
>
|
|
341
|
+
{children}
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export default SwipeableCard;
|
package/src/components/Text.tsx
CHANGED
|
@@ -5,13 +5,21 @@ import React, { forwardRef } from 'react';
|
|
|
5
5
|
|
|
6
6
|
type TextElement = 'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label';
|
|
7
7
|
|
|
8
|
+
type TextSize = 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl';
|
|
9
|
+
|
|
8
10
|
export interface TextProps extends Omit<React.HTMLAttributes<HTMLElement>, 'color'> {
|
|
9
11
|
/** Text content */
|
|
10
12
|
children: React.ReactNode;
|
|
11
13
|
/** HTML element to render */
|
|
12
14
|
as?: TextElement;
|
|
13
|
-
/** Size variant */
|
|
14
|
-
size?:
|
|
15
|
+
/** Size variant (base size) */
|
|
16
|
+
size?: TextSize;
|
|
17
|
+
/** Size on small screens (640px+) - overrides base size */
|
|
18
|
+
smSize?: TextSize;
|
|
19
|
+
/** Size on medium screens (768px+) - overrides smaller breakpoints */
|
|
20
|
+
mdSize?: TextSize;
|
|
21
|
+
/** Size on large screens (1024px+) - overrides smaller breakpoints */
|
|
22
|
+
lgSize?: TextSize;
|
|
15
23
|
/** Weight variant */
|
|
16
24
|
weight?: 'normal' | 'medium' | 'semibold' | 'bold';
|
|
17
25
|
/** Color variant */
|
|
@@ -58,6 +66,9 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
|
|
|
58
66
|
children,
|
|
59
67
|
as: Component = 'p',
|
|
60
68
|
size = 'base',
|
|
69
|
+
smSize,
|
|
70
|
+
mdSize,
|
|
71
|
+
lgSize,
|
|
61
72
|
weight = 'normal',
|
|
62
73
|
color = 'primary',
|
|
63
74
|
align = 'left',
|
|
@@ -67,7 +78,7 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
|
|
|
67
78
|
className = '',
|
|
68
79
|
...htmlProps
|
|
69
80
|
}, ref) => {
|
|
70
|
-
const sizeClasses = {
|
|
81
|
+
const sizeClasses: Record<TextSize, string> = {
|
|
71
82
|
xs: 'text-xs',
|
|
72
83
|
sm: 'text-sm',
|
|
73
84
|
base: 'text-base',
|
|
@@ -76,6 +87,34 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
|
|
|
76
87
|
'2xl': 'text-2xl',
|
|
77
88
|
};
|
|
78
89
|
|
|
90
|
+
// Responsive size classes
|
|
91
|
+
const smSizeClasses: Record<TextSize, string> = {
|
|
92
|
+
xs: 'sm:text-xs',
|
|
93
|
+
sm: 'sm:text-sm',
|
|
94
|
+
base: 'sm:text-base',
|
|
95
|
+
lg: 'sm:text-lg',
|
|
96
|
+
xl: 'sm:text-xl',
|
|
97
|
+
'2xl': 'sm:text-2xl',
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const mdSizeClasses: Record<TextSize, string> = {
|
|
101
|
+
xs: 'md:text-xs',
|
|
102
|
+
sm: 'md:text-sm',
|
|
103
|
+
base: 'md:text-base',
|
|
104
|
+
lg: 'md:text-lg',
|
|
105
|
+
xl: 'md:text-xl',
|
|
106
|
+
'2xl': 'md:text-2xl',
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const lgSizeClasses: Record<TextSize, string> = {
|
|
110
|
+
xs: 'lg:text-xs',
|
|
111
|
+
sm: 'lg:text-sm',
|
|
112
|
+
base: 'lg:text-base',
|
|
113
|
+
lg: 'lg:text-lg',
|
|
114
|
+
xl: 'lg:text-xl',
|
|
115
|
+
'2xl': 'lg:text-2xl',
|
|
116
|
+
};
|
|
117
|
+
|
|
79
118
|
const weightClasses = {
|
|
80
119
|
normal: 'font-normal',
|
|
81
120
|
medium: 'font-medium',
|
|
@@ -118,6 +157,9 @@ export const Text = forwardRef<HTMLElement, TextProps>(({
|
|
|
118
157
|
// Build class list
|
|
119
158
|
const classes = [
|
|
120
159
|
sizeClasses[size],
|
|
160
|
+
smSize ? smSizeClasses[smSize] : '',
|
|
161
|
+
mdSize ? mdSizeClasses[mdSize] : '',
|
|
162
|
+
lgSize ? lgSizeClasses[lgSize] : '',
|
|
121
163
|
weightClasses[weight],
|
|
122
164
|
colorClasses[color],
|
|
123
165
|
alignClasses[align],
|
package/src/components/index.ts
CHANGED
|
@@ -189,8 +189,8 @@ export type { ContextMenuProps } from './ContextMenu';
|
|
|
189
189
|
export { default as ErrorBoundary } from './ErrorBoundary';
|
|
190
190
|
export type { ErrorBoundaryProps } from './ErrorBoundary';
|
|
191
191
|
|
|
192
|
-
export { default as BottomSheet } from './BottomSheet';
|
|
193
|
-
export type { BottomSheetProps } from './BottomSheet';
|
|
192
|
+
export { default as BottomSheet, BottomSheetHeader, BottomSheetContent, BottomSheetActions } from './BottomSheet';
|
|
193
|
+
export type { BottomSheetProps, BottomSheetHeaderProps, BottomSheetContentProps, BottomSheetActionsProps } from './BottomSheet';
|
|
194
194
|
|
|
195
195
|
export { default as Collapsible } from './Collapsible';
|
|
196
196
|
export type { CollapsibleProps } from './Collapsible';
|
|
@@ -200,6 +200,19 @@ export type { ExpandablePanelProps } from './ExpandablePanel';
|
|
|
200
200
|
|
|
201
201
|
export { Show, Hide } from './ResponsiveUtilities';
|
|
202
202
|
|
|
203
|
+
// Mobile Components
|
|
204
|
+
export { default as HorizontalScroll } from './HorizontalScroll';
|
|
205
|
+
export type { HorizontalScrollProps } from './HorizontalScroll';
|
|
206
|
+
|
|
207
|
+
export { default as SwipeableCard } from './SwipeableCard';
|
|
208
|
+
export type { SwipeableCardProps, SwipeAction as SwipeableCardAction } from './SwipeableCard';
|
|
209
|
+
|
|
210
|
+
export { default as NotificationBanner } from './NotificationBanner';
|
|
211
|
+
export type { NotificationBannerProps, NotificationBannerAction } from './NotificationBanner';
|
|
212
|
+
|
|
213
|
+
export { default as CompactStat } from './CompactStat';
|
|
214
|
+
export type { CompactStatProps, CompactStatTrend } from './CompactStat';
|
|
215
|
+
|
|
203
216
|
// Navigation Components
|
|
204
217
|
export { default as Breadcrumbs, useBreadcrumbReset } from './Breadcrumbs';
|
|
205
218
|
export type { BreadcrumbsProps, BreadcrumbItem, BreadcrumbNavigationState } from './Breadcrumbs';
|
|
@@ -262,7 +275,7 @@ export type { MobileHeaderProps } from './MobileHeader';
|
|
|
262
275
|
export { default as FloatingActionButton, useFABScroll } from './FloatingActionButton';
|
|
263
276
|
export type { FloatingActionButtonProps, FABAction } from './FloatingActionButton';
|
|
264
277
|
|
|
265
|
-
export { default as PullToRefresh
|
|
278
|
+
export { default as PullToRefresh } from './PullToRefresh';
|
|
266
279
|
export type { PullToRefreshProps } from './PullToRefresh';
|
|
267
280
|
|
|
268
281
|
// Logo
|
package/src/styles/index.css
CHANGED
|
@@ -586,3 +586,35 @@
|
|
|
586
586
|
.animate-slide-in-bottom {
|
|
587
587
|
animation: slideInBottom 0.3s ease-out;
|
|
588
588
|
}
|
|
589
|
+
|
|
590
|
+
/* Touch-friendly sizing for coarse pointer devices (touch screens) */
|
|
591
|
+
@media (pointer: coarse) {
|
|
592
|
+
/* Ensure buttons meet 48px minimum touch target on touch devices */
|
|
593
|
+
.btn,
|
|
594
|
+
button.touch-friendly,
|
|
595
|
+
.touch-target {
|
|
596
|
+
min-height: 48px;
|
|
597
|
+
min-width: 48px;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/* Larger tap targets for inputs on touch devices */
|
|
601
|
+
.input,
|
|
602
|
+
input[type="text"],
|
|
603
|
+
input[type="email"],
|
|
604
|
+
input[type="password"],
|
|
605
|
+
input[type="number"],
|
|
606
|
+
input[type="tel"],
|
|
607
|
+
input[type="url"],
|
|
608
|
+
input[type="search"],
|
|
609
|
+
select,
|
|
610
|
+
textarea {
|
|
611
|
+
min-height: 48px;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/* Larger checkbox/radio hit areas */
|
|
615
|
+
input[type="checkbox"],
|
|
616
|
+
input[type="radio"] {
|
|
617
|
+
min-width: 24px;
|
|
618
|
+
min-height: 24px;
|
|
619
|
+
}
|
|
620
|
+
}
|