@proulr/sdui-components 0.1.56 → 0.1.57
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/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import React, { useCallback, useRef, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
3
|
import {
|
|
4
|
+
LayoutAnimation,
|
|
4
5
|
NativeSyntheticEvent,
|
|
5
6
|
Platform,
|
|
6
7
|
Pressable,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
Text,
|
|
9
10
|
TextInput,
|
|
10
11
|
TextInputContentSizeChangeEventData,
|
|
12
|
+
UIManager,
|
|
11
13
|
View,
|
|
12
14
|
type StyleProp,
|
|
13
15
|
type TextInputProps,
|
|
@@ -18,6 +20,10 @@ import { AppIcon } from './app-icon';
|
|
|
18
20
|
import { ChromeGlassLayer } from './ChromeGlass';
|
|
19
21
|
import { colors, fonts, m3Colors, m3Shape, spacing } from '../theme/proulr-theme';
|
|
20
22
|
|
|
23
|
+
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
|
|
24
|
+
UIManager.setLayoutAnimationEnabledExperimental(true);
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
const ACTION_BUTTON_SIZE = 48;
|
|
22
28
|
/** Filled action circle when nested inside the pill (Instagram-style composer). */
|
|
23
29
|
const ACTION_BUTTON_SIZE_INSET = 36;
|
|
@@ -198,10 +204,12 @@ export function ComposePillTextField({
|
|
|
198
204
|
editable={editable}
|
|
199
205
|
style={[
|
|
200
206
|
styles.input,
|
|
207
|
+
isMultiline ? styles.inputMultiline : styles.inputSingleLine,
|
|
201
208
|
{
|
|
202
209
|
color: variantColors.text,
|
|
203
|
-
|
|
204
|
-
|
|
210
|
+
...(isMultiline
|
|
211
|
+
? { maxHeight: inputMaxHeight, textAlignVertical: 'top' as const }
|
|
212
|
+
: { textAlignVertical: 'center' as const }),
|
|
205
213
|
},
|
|
206
214
|
style,
|
|
207
215
|
]}
|
|
@@ -230,19 +238,33 @@ export type ComposePillInputProps = {
|
|
|
230
238
|
onFocus?: () => void;
|
|
231
239
|
onBlur?: () => void;
|
|
232
240
|
inputRef?: ComposePillTextInputRef;
|
|
241
|
+
/** Inside the pill (camera / location circle, etc.). */
|
|
233
242
|
leading?: ReactNode;
|
|
243
|
+
/** Before the pill (avatar in comments). */
|
|
244
|
+
leadingOutside?: ReactNode;
|
|
234
245
|
trailing?: ReactNode;
|
|
235
246
|
endAction?: ReactNode;
|
|
236
247
|
/**
|
|
237
|
-
* `outside` — send
|
|
238
|
-
* `inside` — action nested in the pill
|
|
248
|
+
* `outside` — send circle beside the pill.
|
|
249
|
+
* `inside` — action nested in the pill.
|
|
250
|
+
* Implied `inside` when `actionSwap` is on.
|
|
239
251
|
*/
|
|
240
252
|
endActionPlacement?: ComposePillEndActionPlacement;
|
|
253
|
+
/**
|
|
254
|
+
* System-wide Instagram composer: hide `trailing` when the field has text and
|
|
255
|
+
* show `endAction` in its place inside the pill (with a short layout animation).
|
|
256
|
+
*/
|
|
257
|
+
actionSwap?: boolean;
|
|
241
258
|
replyPreview?: ComposePillReplyPreview | null;
|
|
242
259
|
style?: StyleProp<ViewStyle>;
|
|
243
260
|
transparentWrap?: boolean;
|
|
244
|
-
/** Bare row without pill chrome — text field only grows inline
|
|
261
|
+
/** Bare row without pill chrome — text field only grows inline. */
|
|
245
262
|
bare?: boolean;
|
|
263
|
+
/**
|
|
264
|
+
* Drop composer row insets — use when the pill sits inside an already-padded
|
|
265
|
+
* dock (profile quick message beside FABs).
|
|
266
|
+
*/
|
|
267
|
+
flush?: boolean;
|
|
246
268
|
rowAlignItems?: 'center' | 'flex-end';
|
|
247
269
|
textInputProps?: Omit<
|
|
248
270
|
ComposePillTextFieldProps,
|
|
@@ -392,20 +414,37 @@ export function ComposePillInput({
|
|
|
392
414
|
onBlur,
|
|
393
415
|
inputRef,
|
|
394
416
|
leading,
|
|
417
|
+
leadingOutside,
|
|
395
418
|
trailing,
|
|
396
419
|
endAction,
|
|
397
420
|
endActionPlacement = 'outside',
|
|
421
|
+
actionSwap = false,
|
|
398
422
|
replyPreview,
|
|
399
423
|
style,
|
|
400
424
|
transparentWrap = false,
|
|
401
425
|
bare = false,
|
|
426
|
+
flush = false,
|
|
402
427
|
rowAlignItems = bare ? 'center' : 'flex-end',
|
|
403
428
|
textInputProps,
|
|
404
429
|
}: ComposePillInputProps) {
|
|
405
430
|
const layout = useComposePillLineLayout(value, { maxLines });
|
|
406
431
|
const { isMultiline, pillHeight, resetLineCount } = layout;
|
|
407
432
|
const variantColors = composePillVariantColors(variant);
|
|
408
|
-
const
|
|
433
|
+
const hasText = Boolean(value.trim());
|
|
434
|
+
const endActionInside = (actionSwap || endActionPlacement === 'inside') && !bare;
|
|
435
|
+
const visibleTrailing = actionSwap && hasText ? null : trailing;
|
|
436
|
+
const visibleEndAction = actionSwap && !hasText ? null : endAction;
|
|
437
|
+
const hadTextRef = useRef(hasText);
|
|
438
|
+
|
|
439
|
+
useEffect(() => {
|
|
440
|
+
if (!actionSwap) {
|
|
441
|
+
hadTextRef.current = hasText;
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (hadTextRef.current === hasText) return;
|
|
445
|
+
hadTextRef.current = hasText;
|
|
446
|
+
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
|
447
|
+
}, [actionSwap, hasText]);
|
|
409
448
|
|
|
410
449
|
const handleChange = useCallback(
|
|
411
450
|
(text: string) => {
|
|
@@ -435,10 +474,10 @@ export function ComposePillInput({
|
|
|
435
474
|
);
|
|
436
475
|
|
|
437
476
|
const trailingSlot =
|
|
438
|
-
|
|
477
|
+
visibleTrailing || (endActionInside && visibleEndAction) ? (
|
|
439
478
|
<View style={[styles.trailingIcons, isMultiline && styles.trailingIconsExpanded]}>
|
|
440
|
-
{
|
|
441
|
-
{endActionInside ?
|
|
479
|
+
{visibleTrailing}
|
|
480
|
+
{endActionInside ? visibleEndAction : null}
|
|
442
481
|
</View>
|
|
443
482
|
) : null;
|
|
444
483
|
|
|
@@ -453,12 +492,20 @@ export function ComposePillInput({
|
|
|
453
492
|
>
|
|
454
493
|
{replyPreview ? <ComposePillReplyBar preview={replyPreview} variant={variant} /> : null}
|
|
455
494
|
|
|
456
|
-
<View
|
|
495
|
+
<View
|
|
496
|
+
style={[
|
|
497
|
+
styles.composerRow,
|
|
498
|
+
flush && styles.composerRowFlush,
|
|
499
|
+
{ alignItems: rowAlignItems },
|
|
500
|
+
]}
|
|
501
|
+
>
|
|
502
|
+
{leadingOutside}
|
|
457
503
|
{bare ? (
|
|
458
504
|
<>
|
|
459
505
|
{leading}
|
|
460
506
|
{textField}
|
|
461
|
-
{
|
|
507
|
+
{visibleTrailing}
|
|
508
|
+
{!endActionInside ? visibleEndAction : null}
|
|
462
509
|
</>
|
|
463
510
|
) : (
|
|
464
511
|
<View
|
|
@@ -483,7 +530,7 @@ export function ComposePillInput({
|
|
|
483
530
|
</View>
|
|
484
531
|
)}
|
|
485
532
|
|
|
486
|
-
{!endActionInside ?
|
|
533
|
+
{!endActionInside ? visibleEndAction : null}
|
|
487
534
|
</View>
|
|
488
535
|
</View>
|
|
489
536
|
);
|
|
@@ -549,6 +596,11 @@ const styles = StyleSheet.create({
|
|
|
549
596
|
paddingHorizontal: spacing.sm,
|
|
550
597
|
paddingTop: spacing.sm,
|
|
551
598
|
},
|
|
599
|
+
composerRowFlush: {
|
|
600
|
+
paddingHorizontal: 0,
|
|
601
|
+
paddingTop: 0,
|
|
602
|
+
gap: 0,
|
|
603
|
+
},
|
|
552
604
|
inputPill: {
|
|
553
605
|
flex: 1,
|
|
554
606
|
flexDirection: 'row',
|
|
@@ -592,9 +644,33 @@ const styles = StyleSheet.create({
|
|
|
592
644
|
lineHeight: LINE_HEIGHT,
|
|
593
645
|
fontFamily: fonts.regular,
|
|
594
646
|
paddingHorizontal: spacing.xs,
|
|
647
|
+
zIndex: 1,
|
|
648
|
+
},
|
|
649
|
+
/** Single-line: match pill height so placeholder/caret sit optically centered with inset actions. */
|
|
650
|
+
inputSingleLine: {
|
|
651
|
+
height: PILL_MIN_HEIGHT,
|
|
652
|
+
minHeight: PILL_MIN_HEIGHT,
|
|
653
|
+
maxHeight: PILL_MIN_HEIGHT,
|
|
654
|
+
paddingTop: 0,
|
|
655
|
+
paddingBottom: 0,
|
|
656
|
+
...Platform.select({
|
|
657
|
+
ios: {
|
|
658
|
+
// Multiline TextInput on iOS ignores textAlignVertical — pad to center the glyph.
|
|
659
|
+
paddingTop: Math.max(0, (PILL_MIN_HEIGHT - LINE_HEIGHT) / 2 - 1),
|
|
660
|
+
paddingBottom: Math.max(0, (PILL_MIN_HEIGHT - LINE_HEIGHT) / 2 + 1),
|
|
661
|
+
},
|
|
662
|
+
android: {
|
|
663
|
+
textAlignVertical: 'center',
|
|
664
|
+
},
|
|
665
|
+
default: {
|
|
666
|
+
paddingTop: VERTICAL_PAD,
|
|
667
|
+
paddingBottom: VERTICAL_PAD,
|
|
668
|
+
},
|
|
669
|
+
}),
|
|
670
|
+
},
|
|
671
|
+
inputMultiline: {
|
|
595
672
|
paddingTop: VERTICAL_PAD,
|
|
596
673
|
paddingBottom: VERTICAL_PAD,
|
|
597
|
-
zIndex: 1,
|
|
598
674
|
},
|
|
599
675
|
bareInput: {
|
|
600
676
|
minHeight: 40,
|