panelui-native 0.25.0 → 0.30.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/README.md +18 -1
- package/lib/module/components/accordion/index.js +8 -8
- package/lib/module/components/accordion/index.js.map +1 -1
- package/lib/module/components/alert/index.js +2 -2
- package/lib/module/components/alert/index.js.map +1 -1
- package/lib/module/components/area-chart/index.js +182 -50
- package/lib/module/components/area-chart/index.js.map +1 -1
- package/lib/module/components/attachment/index.js +2 -1
- package/lib/module/components/attachment/index.js.map +1 -1
- package/lib/module/components/avatar/index.js +3 -3
- package/lib/module/components/avatar/index.js.map +1 -1
- package/lib/module/components/badge/index.js +4 -4
- package/lib/module/components/badge/index.js.map +1 -1
- package/lib/module/components/bar-chart/index.js +197 -56
- package/lib/module/components/bar-chart/index.js.map +1 -1
- package/lib/module/components/bottom-sheet/index.js +15 -10
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/breadcrumb/index.js +6 -6
- package/lib/module/components/breadcrumb/index.js.map +1 -1
- package/lib/module/components/button/index.js +11 -6
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/calendar/index.js +792 -0
- package/lib/module/components/calendar/index.js.map +1 -0
- package/lib/module/components/carousel/index.js +680 -0
- package/lib/module/components/carousel/index.js.map +1 -0
- package/lib/module/components/chip/index.js +4 -4
- package/lib/module/components/chip/index.js.map +1 -1
- package/lib/module/components/code-block/highlight.js +363 -0
- package/lib/module/components/code-block/highlight.js.map +1 -0
- package/lib/module/components/code-block/index.js +313 -0
- package/lib/module/components/code-block/index.js.map +1 -0
- package/lib/module/components/date-picker/index.js +165 -0
- package/lib/module/components/date-picker/index.js.map +1 -0
- package/lib/module/components/dialog/index.js +7 -2
- package/lib/module/components/dialog/index.js.map +1 -1
- package/lib/module/components/direction/index.js +2 -1
- package/lib/module/components/direction/index.js.map +1 -1
- package/lib/module/components/empty-state/index.js +2 -2
- package/lib/module/components/empty-state/index.js.map +1 -1
- package/lib/module/components/field/index.js +5 -5
- package/lib/module/components/field/index.js.map +1 -1
- package/lib/module/components/flow/index.js +3 -3
- package/lib/module/components/flow/index.js.map +1 -1
- package/lib/module/components/frame/index.js +5 -5
- package/lib/module/components/frame/index.js.map +1 -1
- package/lib/module/components/input-group/index.js +3 -2
- package/lib/module/components/input-group/index.js.map +1 -1
- package/lib/module/components/item/index.js +9 -9
- package/lib/module/components/item/index.js.map +1 -1
- package/lib/module/components/label/index.js +4 -4
- package/lib/module/components/label/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +167 -44
- package/lib/module/components/line-chart/index.js.map +1 -1
- package/lib/module/components/map/index.js +7 -7
- package/lib/module/components/map/index.js.map +1 -1
- package/lib/module/components/marker/index.js +3 -3
- package/lib/module/components/marker/index.js.map +1 -1
- package/lib/module/components/menu/index.js +714 -0
- package/lib/module/components/menu/index.js.map +1 -0
- package/lib/module/components/message/index.js +7 -7
- package/lib/module/components/message/index.js.map +1 -1
- package/lib/module/components/message-scroller/index.js +5 -4
- package/lib/module/components/message-scroller/index.js.map +1 -1
- package/lib/module/components/plan/index.js +296 -0
- package/lib/module/components/plan/index.js.map +1 -0
- package/lib/module/components/popover/index.js +73 -10
- package/lib/module/components/popover/index.js.map +1 -1
- package/lib/module/components/radio-group/index.js +15 -10
- package/lib/module/components/radio-group/index.js.map +1 -1
- package/lib/module/components/reasoning/index.js +244 -0
- package/lib/module/components/reasoning/index.js.map +1 -0
- package/lib/module/components/section-rail/index.js +4 -4
- package/lib/module/components/section-rail/index.js.map +1 -1
- package/lib/module/components/select/index.js +10 -4
- package/lib/module/components/select/index.js.map +1 -1
- package/lib/module/components/sources/index.js +221 -0
- package/lib/module/components/sources/index.js.map +1 -0
- package/lib/module/components/steps/index.js +4 -4
- package/lib/module/components/steps/index.js.map +1 -1
- package/lib/module/components/tabs/index.js +7 -7
- package/lib/module/components/tabs/index.js.map +1 -1
- package/lib/module/components/task/index.js +300 -0
- package/lib/module/components/task/index.js.map +1 -0
- package/lib/module/components/textarea/index.js +295 -0
- package/lib/module/components/textarea/index.js.map +1 -0
- package/lib/module/components/timeline/index.js +4 -4
- package/lib/module/components/timeline/index.js.map +1 -1
- package/lib/module/components/toast/index.js +2 -2
- package/lib/module/components/toast/index.js.map +1 -1
- package/lib/module/components/toggle-button/index.js +5 -5
- package/lib/module/components/toggle-button/index.js.map +1 -1
- package/lib/module/components/tooltip/index.js +648 -0
- package/lib/module/components/tooltip/index.js.map +1 -0
- package/lib/module/components/typography/index.js +2 -2
- package/lib/module/components/typography/index.js.map +1 -1
- package/lib/module/hooks/index.js +1 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/use-back-handler.js +37 -0
- package/lib/module/hooks/use-back-handler.js.map +1 -0
- package/lib/module/icons/index.js +202 -0
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +13 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/collapse.js +112 -0
- package/lib/module/primitives/collapse.js.map +1 -0
- package/lib/module/primitives/keyboard-avoider.js +2 -1
- package/lib/module/primitives/keyboard-avoider.js.map +1 -1
- package/lib/module/primitives/text.js +25 -1
- package/lib/module/primitives/text.js.map +1 -1
- package/lib/module/utils/date.js +386 -0
- package/lib/module/utils/date.js.map +1 -0
- package/lib/typescript/src/components/accordion/index.d.ts.map +1 -1
- package/lib/typescript/src/components/alert/index.d.ts.map +1 -1
- package/lib/typescript/src/components/area-chart/index.d.ts +41 -1
- package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/attachment/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bar-chart/index.d.ts +44 -2
- package/lib/typescript/src/components/bar-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/breadcrumb/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/calendar/index.d.ts +173 -0
- package/lib/typescript/src/components/calendar/index.d.ts.map +1 -0
- package/lib/typescript/src/components/carousel/index.d.ts +179 -0
- package/lib/typescript/src/components/carousel/index.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/index.d.ts.map +1 -1
- package/lib/typescript/src/components/code-block/highlight.d.ts +39 -0
- package/lib/typescript/src/components/code-block/highlight.d.ts.map +1 -0
- package/lib/typescript/src/components/code-block/index.d.ts +113 -0
- package/lib/typescript/src/components/code-block/index.d.ts.map +1 -0
- package/lib/typescript/src/components/date-picker/index.d.ts +97 -0
- package/lib/typescript/src/components/date-picker/index.d.ts.map +1 -0
- package/lib/typescript/src/components/dialog/index.d.ts.map +1 -1
- package/lib/typescript/src/components/direction/index.d.ts.map +1 -1
- package/lib/typescript/src/components/empty-state/index.d.ts.map +1 -1
- package/lib/typescript/src/components/field/index.d.ts.map +1 -1
- package/lib/typescript/src/components/frame/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/item/index.d.ts.map +1 -1
- package/lib/typescript/src/components/label/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts +41 -2
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/map/index.d.ts.map +1 -1
- package/lib/typescript/src/components/marker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/menu/index.d.ts +261 -0
- package/lib/typescript/src/components/menu/index.d.ts.map +1 -0
- package/lib/typescript/src/components/message/index.d.ts.map +1 -1
- package/lib/typescript/src/components/message-scroller/index.d.ts.map +1 -1
- package/lib/typescript/src/components/plan/index.d.ts +127 -0
- package/lib/typescript/src/components/plan/index.d.ts.map +1 -0
- package/lib/typescript/src/components/popover/index.d.ts +35 -1
- package/lib/typescript/src/components/popover/index.d.ts.map +1 -1
- package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/reasoning/index.d.ts +97 -0
- package/lib/typescript/src/components/reasoning/index.d.ts.map +1 -0
- package/lib/typescript/src/components/select/index.d.ts.map +1 -1
- package/lib/typescript/src/components/sources/index.d.ts +85 -0
- package/lib/typescript/src/components/sources/index.d.ts.map +1 -0
- package/lib/typescript/src/components/steps/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
- package/lib/typescript/src/components/task/index.d.ts +103 -0
- package/lib/typescript/src/components/task/index.d.ts.map +1 -0
- package/lib/typescript/src/components/textarea/index.d.ts +175 -0
- package/lib/typescript/src/components/textarea/index.d.ts.map +1 -0
- package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
- package/lib/typescript/src/components/toggle-button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tooltip/index.d.ts +180 -0
- package/lib/typescript/src/components/tooltip/index.d.ts.map +1 -0
- package/lib/typescript/src/components/typography/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/index.d.ts +1 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-back-handler.d.ts +19 -0
- package/lib/typescript/src/hooks/use-back-handler.d.ts.map +1 -0
- package/lib/typescript/src/icons/index.d.ts +17 -0
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +16 -4
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/collapse.d.ts +42 -0
- package/lib/typescript/src/primitives/collapse.d.ts.map +1 -0
- package/lib/typescript/src/primitives/keyboard-avoider.d.ts.map +1 -1
- package/lib/typescript/src/primitives/text.d.ts +20 -0
- package/lib/typescript/src/primitives/text.d.ts.map +1 -1
- package/lib/typescript/src/utils/date.d.ts +116 -0
- package/lib/typescript/src/utils/date.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/accordion/index.tsx +8 -12
- package/src/components/alert/index.tsx +2 -2
- package/src/components/area-chart/index.tsx +208 -55
- package/src/components/attachment/index.tsx +2 -2
- package/src/components/avatar/index.tsx +3 -3
- package/src/components/badge/index.tsx +6 -6
- package/src/components/bar-chart/index.tsx +214 -54
- package/src/components/bottom-sheet/index.tsx +17 -16
- package/src/components/breadcrumb/index.tsx +6 -8
- package/src/components/button/index.tsx +12 -8
- package/src/components/calendar/index.tsx +970 -0
- package/src/components/carousel/index.tsx +788 -0
- package/src/components/chip/index.tsx +4 -6
- package/src/components/code-block/highlight.ts +278 -0
- package/src/components/code-block/index.tsx +341 -0
- package/src/components/date-picker/index.tsx +255 -0
- package/src/components/dialog/index.tsx +7 -2
- package/src/components/direction/index.tsx +2 -1
- package/src/components/empty-state/index.tsx +2 -2
- package/src/components/field/index.tsx +3 -5
- package/src/components/flow/index.tsx +3 -3
- package/src/components/frame/index.tsx +5 -7
- package/src/components/input-group/index.tsx +3 -2
- package/src/components/item/index.tsx +9 -9
- package/src/components/label/index.tsx +2 -2
- package/src/components/line-chart/index.tsx +189 -50
- package/src/components/map/index.tsx +7 -9
- package/src/components/marker/index.tsx +3 -3
- package/src/components/menu/index.tsx +812 -0
- package/src/components/message/index.tsx +7 -7
- package/src/components/message-scroller/index.tsx +5 -4
- package/src/components/plan/index.tsx +325 -0
- package/src/components/popover/index.tsx +111 -8
- package/src/components/radio-group/index.tsx +13 -4
- package/src/components/reasoning/index.tsx +297 -0
- package/src/components/section-rail/index.tsx +4 -4
- package/src/components/select/index.tsx +10 -4
- package/src/components/sources/index.tsx +278 -0
- package/src/components/steps/index.tsx +4 -4
- package/src/components/tabs/index.tsx +7 -9
- package/src/components/task/index.tsx +329 -0
- package/src/components/textarea/index.tsx +381 -0
- package/src/components/timeline/index.tsx +4 -4
- package/src/components/toast/index.tsx +2 -2
- package/src/components/toggle-button/index.tsx +5 -7
- package/src/components/tooltip/index.tsx +793 -0
- package/src/components/typography/index.tsx +2 -2
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-back-handler.ts +37 -0
- package/src/icons/index.tsx +147 -0
- package/src/index.ts +116 -0
- package/src/primitives/collapse.tsx +128 -0
- package/src/primitives/keyboard-avoider.tsx +2 -1
- package/src/primitives/text.tsx +28 -1
- package/src/utils/date.ts +446 -0
- package/theme.css +112 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Textarea — a field for text that runs to several lines.
|
|
3
|
+
*
|
|
4
|
+
* It shares Input's focus treatment: the border crosses between two colours on
|
|
5
|
+
* the UI thread over 150ms rather than snapping, because a border that switches
|
|
6
|
+
* wholesale reads as a redraw instead of as the field answering you.
|
|
7
|
+
*
|
|
8
|
+
* Height comes from `rows` rather than from a class, so it composes with `size`
|
|
9
|
+
* instead of fighting it — three rows of 14px text and three rows of 16px text
|
|
10
|
+
* are different heights, and both are three rows. `autoGrow` then lets the
|
|
11
|
+
* field follow its content up to `maxRows`, after which it scrolls, which is
|
|
12
|
+
* the behaviour a composer wants: it starts small, opens up as you write, and
|
|
13
|
+
* stops before it eats the screen.
|
|
14
|
+
*
|
|
15
|
+
* ```tsx
|
|
16
|
+
* <Textarea label="Notes" placeholder="Anything we should know?" />
|
|
17
|
+
* <Textarea autoGrow rows={2} maxRows={8} showCount maxLength={280} />
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* For a single line, reach for `Input`.
|
|
21
|
+
*/
|
|
22
|
+
import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
|
|
23
|
+
import {
|
|
24
|
+
TextInput,
|
|
25
|
+
View,
|
|
26
|
+
type NativeSyntheticEvent,
|
|
27
|
+
type TextInputContentSizeChangeEventData,
|
|
28
|
+
type TextInputProps,
|
|
29
|
+
} from 'react-native';
|
|
30
|
+
import Animated, {
|
|
31
|
+
interpolateColor,
|
|
32
|
+
useAnimatedStyle,
|
|
33
|
+
useSharedValue,
|
|
34
|
+
withTiming,
|
|
35
|
+
} from 'react-native-reanimated';
|
|
36
|
+
import { tv, type VariantProps } from 'tailwind-variants';
|
|
37
|
+
import { useCSSVariable } from 'uniwind';
|
|
38
|
+
import type { KeyboardAvoidanceMode } from '../../hooks/use-keyboard-avoidance';
|
|
39
|
+
import { KeyboardAvoider } from '../../primitives/keyboard-avoider';
|
|
40
|
+
import { Text } from '../../primitives/text';
|
|
41
|
+
import { Label } from '../label';
|
|
42
|
+
|
|
43
|
+
/** Long enough to read as a transition, short enough not to lag a fast tab. */
|
|
44
|
+
const FOCUS_DURATION = 150;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Type size and the leading it is given, per `size`.
|
|
48
|
+
*
|
|
49
|
+
* The height of the box is derived from these rather than set as a class,
|
|
50
|
+
* because `rows` is a count and a count only becomes a height once you know how
|
|
51
|
+
* tall a line is. They are the same numbers the classes below carry; keeping
|
|
52
|
+
* them here as well is what lets `rows` and `size` compose.
|
|
53
|
+
*/
|
|
54
|
+
const METRICS = {
|
|
55
|
+
sm: { fontSize: 14, lineHeight: 20, padding: 10 },
|
|
56
|
+
md: { fontSize: 16, lineHeight: 22, padding: 12 },
|
|
57
|
+
lg: { fontSize: 16, lineHeight: 24, padding: 14 },
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
const textareaVariants = tv({
|
|
61
|
+
slots: {
|
|
62
|
+
container: 'w-full gap-1.5',
|
|
63
|
+
/*
|
|
64
|
+
* No height here: it is computed from `rows` and applied as a style. Text
|
|
65
|
+
* starts at the top rather than floating in the middle of an empty box, and
|
|
66
|
+
* the leading is wanted — between several lines it separates them, where in
|
|
67
|
+
* a one-line field it would push the text off centre.
|
|
68
|
+
*/
|
|
69
|
+
field: 'w-full rounded-lg border text-foreground',
|
|
70
|
+
footer: 'w-full flex-row items-start justify-between gap-3',
|
|
71
|
+
description: 'text-sm text-muted-foreground',
|
|
72
|
+
error: 'text-sm text-destructive',
|
|
73
|
+
count: 'shrink-0 text-sm tabular-nums text-muted-foreground',
|
|
74
|
+
},
|
|
75
|
+
variants: {
|
|
76
|
+
variant: {
|
|
77
|
+
// The border colour is animated, so it is deliberately not set here —
|
|
78
|
+
// only the background belongs to the class.
|
|
79
|
+
outline: { field: 'bg-background' },
|
|
80
|
+
filled: { field: 'bg-muted' },
|
|
81
|
+
},
|
|
82
|
+
size: {
|
|
83
|
+
sm: { field: 'px-3 text-[14px]' },
|
|
84
|
+
md: { field: 'px-3.5 text-[16px]' },
|
|
85
|
+
lg: { field: 'px-4 text-[16px]' },
|
|
86
|
+
},
|
|
87
|
+
disabled: {
|
|
88
|
+
true: { field: 'opacity-[0.64]' },
|
|
89
|
+
},
|
|
90
|
+
/** Past the limit the counter is the error, so it is tinted like one. */
|
|
91
|
+
over: {
|
|
92
|
+
true: { count: 'text-destructive' },
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
defaultVariants: {
|
|
96
|
+
variant: 'outline',
|
|
97
|
+
size: 'md',
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
type TextareaVariantProps = VariantProps<typeof textareaVariants>;
|
|
102
|
+
|
|
103
|
+
export interface TextareaProps
|
|
104
|
+
extends Omit<TextInputProps, 'multiline' | 'numberOfLines'>,
|
|
105
|
+
Omit<TextareaVariantProps, 'disabled' | 'over'> {
|
|
106
|
+
className?: string;
|
|
107
|
+
containerClassName?: string;
|
|
108
|
+
label?: string;
|
|
109
|
+
description?: string;
|
|
110
|
+
/** Error message. When set, the field renders in its invalid state. */
|
|
111
|
+
errorMessage?: string;
|
|
112
|
+
/** Marks the field required — an asterisk on the label, and the a11y state. */
|
|
113
|
+
isRequired?: boolean;
|
|
114
|
+
disabled?: boolean;
|
|
115
|
+
/** How many lines of text the field is tall before it scrolls or grows. */
|
|
116
|
+
rows?: number;
|
|
117
|
+
/**
|
|
118
|
+
* Grow with the text, one line at a time, up to `maxRows` — after which the
|
|
119
|
+
* field holds its height and scrolls instead. Without it the field stays at
|
|
120
|
+
* `rows` and scrolls from the first line past it.
|
|
121
|
+
*/
|
|
122
|
+
autoGrow?: boolean;
|
|
123
|
+
/** The tallest `autoGrow` will go. Ignored without it. */
|
|
124
|
+
maxRows?: number;
|
|
125
|
+
/**
|
|
126
|
+
* Show how much of `maxLength` is used, under the field. Needs `maxLength`;
|
|
127
|
+
* a counter with no limit to count towards says nothing.
|
|
128
|
+
*/
|
|
129
|
+
showCount?: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Keep the field clear of the software keyboard. Moves by exactly the
|
|
132
|
+
* overlap, and not at all when the field is already clear — or when the
|
|
133
|
+
* keyboard belongs to a different field. The overlap is re-read every frame
|
|
134
|
+
* while the field is focused, so the field keeps its place in the page as it
|
|
135
|
+
* scrolls under and back out of the keyboard.
|
|
136
|
+
*
|
|
137
|
+
* Install `react-native-keyboard-controller` for this to behave on Android.
|
|
138
|
+
*
|
|
139
|
+
* Do not toggle this at runtime — it changes which component renders the
|
|
140
|
+
* container, which would remount the field and drop focus.
|
|
141
|
+
*/
|
|
142
|
+
avoidKeyboard?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* How the field gets clear. `lift` moves it up by its overlap and follows
|
|
145
|
+
* the scroll — right for a field in the flow of a page. `dock` makes it
|
|
146
|
+
* travel with the keyboard, for a composer already pinned near the bottom
|
|
147
|
+
* edge; pair it with `keyboardBottomInset`.
|
|
148
|
+
*/
|
|
149
|
+
keyboardMode?: KeyboardAvoidanceMode;
|
|
150
|
+
/** Gap kept between the field and the keyboard. `keyboardMode="lift"` only. */
|
|
151
|
+
keyboardOffset?: number;
|
|
152
|
+
/**
|
|
153
|
+
* How far above the bottom edge the field already sits — usually the safe
|
|
154
|
+
* area inset. `keyboardMode="dock"` only.
|
|
155
|
+
*/
|
|
156
|
+
keyboardBottomInset?: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
|
|
160
|
+
|
|
161
|
+
export const Textarea = forwardRef<TextInput, TextareaProps>(
|
|
162
|
+
(
|
|
163
|
+
{
|
|
164
|
+
className,
|
|
165
|
+
containerClassName,
|
|
166
|
+
label,
|
|
167
|
+
description,
|
|
168
|
+
errorMessage,
|
|
169
|
+
isRequired,
|
|
170
|
+
disabled,
|
|
171
|
+
variant,
|
|
172
|
+
size = 'md',
|
|
173
|
+
rows = 4,
|
|
174
|
+
autoGrow = false,
|
|
175
|
+
maxRows = 10,
|
|
176
|
+
showCount = false,
|
|
177
|
+
avoidKeyboard = false,
|
|
178
|
+
keyboardMode = 'lift',
|
|
179
|
+
keyboardOffset = 16,
|
|
180
|
+
keyboardBottomInset = 0,
|
|
181
|
+
onFocus,
|
|
182
|
+
onBlur,
|
|
183
|
+
onContentSizeChange,
|
|
184
|
+
style,
|
|
185
|
+
...props
|
|
186
|
+
},
|
|
187
|
+
ref
|
|
188
|
+
) => {
|
|
189
|
+
const [focused, setFocused] = useState(false);
|
|
190
|
+
const invalid = !!errorMessage;
|
|
191
|
+
|
|
192
|
+
const metrics = METRICS[size];
|
|
193
|
+
/* Both paddings plus the lines themselves — the box a row count asks for. */
|
|
194
|
+
const heightFor = useCallback(
|
|
195
|
+
(lines: number) => lines * metrics.lineHeight + metrics.padding * 2,
|
|
196
|
+
[metrics]
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const minHeight = heightFor(rows);
|
|
200
|
+
const maxHeight = autoGrow ? heightFor(Math.max(rows, maxRows)) : undefined;
|
|
201
|
+
|
|
202
|
+
/*
|
|
203
|
+
* What the text actually measures, once it has been laid out. It only ever
|
|
204
|
+
* raises the floor — the field never shrinks below `rows`, and never grows
|
|
205
|
+
* past `maxRows`, so the clamping happens here rather than in the style.
|
|
206
|
+
*/
|
|
207
|
+
const [contentHeight, setContentHeight] = useState(0);
|
|
208
|
+
const grownHeight = autoGrow
|
|
209
|
+
? Math.min(
|
|
210
|
+
Math.max(minHeight, contentHeight + metrics.padding * 2),
|
|
211
|
+
maxHeight ?? Number.POSITIVE_INFINITY
|
|
212
|
+
)
|
|
213
|
+
: minHeight;
|
|
214
|
+
|
|
215
|
+
const length = props.value?.length ?? props.defaultValue?.length ?? 0;
|
|
216
|
+
const over = !!props.maxLength && length >= props.maxLength;
|
|
217
|
+
|
|
218
|
+
const placeholderColor = useCSSVariable('--color-muted-foreground');
|
|
219
|
+
const restColor = useCSSVariable('--color-input');
|
|
220
|
+
const focusColor = useCSSVariable('--color-ring');
|
|
221
|
+
const errorColor = useCSSVariable('--color-destructive');
|
|
222
|
+
|
|
223
|
+
const slots = textareaVariants({
|
|
224
|
+
variant,
|
|
225
|
+
size,
|
|
226
|
+
disabled: !!disabled,
|
|
227
|
+
over,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
/*
|
|
231
|
+
* Border colour is driven by one 0..1 value rather than by a class per
|
|
232
|
+
* state. Uniwind can only swap a class wholesale, which is the snap this
|
|
233
|
+
* is here to avoid, and a shared value crosses between the two colours on
|
|
234
|
+
* the UI thread without a re-render.
|
|
235
|
+
*/
|
|
236
|
+
const focus = useSharedValue(0);
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
focus.value = withTiming(focused ? 1 : 0, { duration: FOCUS_DURATION });
|
|
239
|
+
}, [focused, focus]);
|
|
240
|
+
|
|
241
|
+
const resting = typeof restColor === 'string' ? restColor : '#e5e5e5';
|
|
242
|
+
const active = invalid
|
|
243
|
+
? typeof errorColor === 'string'
|
|
244
|
+
? errorColor
|
|
245
|
+
: '#ef4444'
|
|
246
|
+
: typeof focusColor === 'string'
|
|
247
|
+
? focusColor
|
|
248
|
+
: '#a3a3a3';
|
|
249
|
+
// An invalid field is tinted even at rest — the error is a fact about the
|
|
250
|
+
// value, not about whether the field happens to be focused.
|
|
251
|
+
const idle = invalid ? active : resting;
|
|
252
|
+
|
|
253
|
+
const borderStyle = useAnimatedStyle(() => ({
|
|
254
|
+
borderColor: interpolateColor(focus.value, [0, 1], [idle, active]),
|
|
255
|
+
}));
|
|
256
|
+
|
|
257
|
+
const handleFocus = useCallback<NonNullable<TextInputProps['onFocus']>>(
|
|
258
|
+
(event) => {
|
|
259
|
+
setFocused(true);
|
|
260
|
+
onFocus?.(event);
|
|
261
|
+
},
|
|
262
|
+
[onFocus]
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
const handleBlur = useCallback<NonNullable<TextInputProps['onBlur']>>(
|
|
266
|
+
(event) => {
|
|
267
|
+
setFocused(false);
|
|
268
|
+
onBlur?.(event);
|
|
269
|
+
},
|
|
270
|
+
[onBlur]
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const handleContentSizeChange = useCallback(
|
|
274
|
+
(event: NativeSyntheticEvent<TextInputContentSizeChangeEventData>) => {
|
|
275
|
+
if (autoGrow) {
|
|
276
|
+
setContentHeight(event.nativeEvent.contentSize.height);
|
|
277
|
+
}
|
|
278
|
+
onContentSizeChange?.(event);
|
|
279
|
+
},
|
|
280
|
+
[autoGrow, onContentSizeChange]
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
/*
|
|
284
|
+
* Type metrics go through `style` rather than through classes because the
|
|
285
|
+
* height is computed from them, and a line height set in two places is a
|
|
286
|
+
* line height that will disagree with itself.
|
|
287
|
+
*/
|
|
288
|
+
const boxStyle = useMemo(
|
|
289
|
+
() => ({
|
|
290
|
+
height: grownHeight,
|
|
291
|
+
lineHeight: metrics.lineHeight,
|
|
292
|
+
paddingTop: metrics.padding,
|
|
293
|
+
paddingBottom: metrics.padding,
|
|
294
|
+
// Android starts the caret in the middle of the box without it.
|
|
295
|
+
textAlignVertical: 'top' as const,
|
|
296
|
+
}),
|
|
297
|
+
[grownHeight, metrics]
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const footer =
|
|
301
|
+
errorMessage || description || (showCount && props.maxLength) ? (
|
|
302
|
+
<View className={slots.footer()}>
|
|
303
|
+
{errorMessage ? (
|
|
304
|
+
<Text className={slots.error()}>{errorMessage}</Text>
|
|
305
|
+
) : description ? (
|
|
306
|
+
<Text className={slots.description()}>{description}</Text>
|
|
307
|
+
) : (
|
|
308
|
+
<View className="flex-1" />
|
|
309
|
+
)}
|
|
310
|
+
{showCount && props.maxLength ? (
|
|
311
|
+
<Text className={slots.count()}>
|
|
312
|
+
{length}/{props.maxLength}
|
|
313
|
+
</Text>
|
|
314
|
+
) : null}
|
|
315
|
+
</View>
|
|
316
|
+
) : null;
|
|
317
|
+
|
|
318
|
+
const body = (
|
|
319
|
+
<>
|
|
320
|
+
{label ? (
|
|
321
|
+
<Label isRequired={isRequired} isInvalid={invalid} isDisabled={!!disabled}>
|
|
322
|
+
{label}
|
|
323
|
+
</Label>
|
|
324
|
+
) : null}
|
|
325
|
+
<AnimatedTextInput
|
|
326
|
+
ref={ref}
|
|
327
|
+
multiline
|
|
328
|
+
editable={!disabled}
|
|
329
|
+
onFocus={handleFocus}
|
|
330
|
+
onBlur={handleBlur}
|
|
331
|
+
onContentSizeChange={handleContentSizeChange}
|
|
332
|
+
accessibilityLabel={label}
|
|
333
|
+
accessibilityState={{ disabled: !!disabled }}
|
|
334
|
+
aria-required={isRequired}
|
|
335
|
+
aria-invalid={invalid}
|
|
336
|
+
className={slots.field({ className })}
|
|
337
|
+
// Caller styles come last, but never last enough to drop the
|
|
338
|
+
// animated border or the height the row count asked for.
|
|
339
|
+
style={[boxStyle, borderStyle, style]}
|
|
340
|
+
placeholderTextColor={
|
|
341
|
+
typeof placeholderColor === 'string' ? placeholderColor : undefined
|
|
342
|
+
}
|
|
343
|
+
{...props}
|
|
344
|
+
/>
|
|
345
|
+
{footer}
|
|
346
|
+
</>
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const containerClasses = slots.container({ className: containerClassName });
|
|
350
|
+
|
|
351
|
+
/*
|
|
352
|
+
* The keyboard hook is deliberately behind a component boundary rather
|
|
353
|
+
* than an `enabled` flag. Calling it at all has global consequences —
|
|
354
|
+
* without the keyboard controller installed it falls back to Reanimated's
|
|
355
|
+
* useAnimatedKeyboard, which switches Android out of adjustResize for the
|
|
356
|
+
* whole app. A field that never asked to avoid the keyboard must not do
|
|
357
|
+
* that to every other screen.
|
|
358
|
+
*/
|
|
359
|
+
if (avoidKeyboard) {
|
|
360
|
+
return (
|
|
361
|
+
<KeyboardAvoider
|
|
362
|
+
// Only while *this* field is the one being typed into. Without it
|
|
363
|
+
// every avoiding field on the screen lifts the moment any field
|
|
364
|
+
// anywhere is tapped, and since they all aim at the same gap above
|
|
365
|
+
// the keyboard, they arrive stacked on top of one another.
|
|
366
|
+
active={focused}
|
|
367
|
+
mode={keyboardMode}
|
|
368
|
+
offset={keyboardOffset}
|
|
369
|
+
bottomInset={keyboardBottomInset}
|
|
370
|
+
className={containerClasses}
|
|
371
|
+
>
|
|
372
|
+
{body}
|
|
373
|
+
</KeyboardAvoider>
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return <View className={containerClasses}>{body}</View>;
|
|
378
|
+
}
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
Textarea.displayName = 'Textarea';
|
|
@@ -43,7 +43,7 @@ import { View, type Text as RNText, type ViewProps } from 'react-native';
|
|
|
43
43
|
import { tv } from 'tailwind-variants';
|
|
44
44
|
import { useCSSVariable } from 'uniwind';
|
|
45
45
|
import { IconColorProvider } from '../../icons';
|
|
46
|
-
import { Text, type TextProps } from '../../primitives/text';
|
|
46
|
+
import { Text, type TextProps, textChildren } from '../../primitives/text';
|
|
47
47
|
|
|
48
48
|
export type TimelineVariant = 'dot' | 'icon' | 'numbered' | 'card' | 'compact';
|
|
49
49
|
/** Semantic colour for a single event, independent of progress. */
|
|
@@ -209,7 +209,7 @@ const TimelineRoot = forwardRef<View, TimelineProps>(
|
|
|
209
209
|
return (
|
|
210
210
|
<TimelineContext.Provider value={context}>
|
|
211
211
|
<View ref={ref} className={root({ className })} {...props}>
|
|
212
|
-
{children}
|
|
212
|
+
{textChildren(children)}
|
|
213
213
|
</View>
|
|
214
214
|
</TimelineContext.Provider>
|
|
215
215
|
);
|
|
@@ -247,7 +247,7 @@ const TimelineItem = forwardRef<View, TimelineItemProps>(
|
|
|
247
247
|
return (
|
|
248
248
|
<TimelineItemContext.Provider value={context}>
|
|
249
249
|
<View ref={ref} className={item({ className })} {...props}>
|
|
250
|
-
{children}
|
|
250
|
+
{textChildren(children)}
|
|
251
251
|
</View>
|
|
252
252
|
</TimelineItemContext.Provider>
|
|
253
253
|
);
|
|
@@ -357,7 +357,7 @@ const TimelineContent = forwardRef<View, ViewProps & { className?: string }>(
|
|
|
357
357
|
|
|
358
358
|
return (
|
|
359
359
|
<View ref={ref} className={body({ className })} {...props}>
|
|
360
|
-
{variant === 'card' ? <View className={panel()}>{children}</View> : children}
|
|
360
|
+
{variant === 'card' ? <View className={panel()}>{textChildren(children)}</View> : children}
|
|
361
361
|
</View>
|
|
362
362
|
);
|
|
363
363
|
}
|
|
@@ -39,7 +39,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
39
39
|
import { tv, type VariantProps } from 'tailwind-variants';
|
|
40
40
|
import { useCSSVariable } from 'uniwind';
|
|
41
41
|
import { Portal } from '../../primitives/portal';
|
|
42
|
-
import { Text, type TextProps } from '../../primitives/text';
|
|
42
|
+
import { Text, type TextProps, textChildren } from '../../primitives/text';
|
|
43
43
|
import { cn } from '../../utils/cn';
|
|
44
44
|
import { AlertTriangleIcon, CheckCircleIcon, InfoIcon, XIcon } from '../../icons';
|
|
45
45
|
import { Button, type ButtonProps } from '../button';
|
|
@@ -171,7 +171,7 @@ const ToastRoot = ({ className, variant = 'default', onHide, children, ...props
|
|
|
171
171
|
return (
|
|
172
172
|
<ToastContext.Provider value={context}>
|
|
173
173
|
<View accessibilityRole="alert" accessibilityLiveRegion="polite" className={root({ className })} {...props}>
|
|
174
|
-
{children}
|
|
174
|
+
{textChildren(children)}
|
|
175
175
|
</View>
|
|
176
176
|
</ToastContext.Provider>
|
|
177
177
|
);
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
AnimatedPressable,
|
|
43
43
|
type AnimatedPressableProps,
|
|
44
44
|
} from '../../primitives/animated-pressable';
|
|
45
|
-
import { Text } from '../../primitives/text';
|
|
45
|
+
import { Text, textChildren } from '../../primitives/text';
|
|
46
46
|
import { cn } from '../../utils/cn';
|
|
47
47
|
import { selectionTick } from '../../utils/haptics';
|
|
48
48
|
|
|
@@ -196,7 +196,7 @@ const ToggleButtonGroup = forwardRef<View, ToggleButtonGroupProps>(
|
|
|
196
196
|
className={cn('flex-row items-center gap-1.5', className)}
|
|
197
197
|
{...props}
|
|
198
198
|
>
|
|
199
|
-
{children}
|
|
199
|
+
{textChildren(children)}
|
|
200
200
|
</View>
|
|
201
201
|
</ToggleGroupContext.Provider>
|
|
202
202
|
);
|
|
@@ -347,13 +347,11 @@ const ToggleButtonRoot = forwardRef<View, ToggleButtonProps>(
|
|
|
347
347
|
)}
|
|
348
348
|
{...props}
|
|
349
349
|
>
|
|
350
|
-
{
|
|
350
|
+
{textChildren(children, (text) => (
|
|
351
351
|
<Text className={slots.label({ className: labelClassName })}>
|
|
352
|
-
{
|
|
352
|
+
{text}
|
|
353
353
|
</Text>
|
|
354
|
-
)
|
|
355
|
-
children
|
|
356
|
-
)}
|
|
354
|
+
))}
|
|
357
355
|
</AnimatedPressable>
|
|
358
356
|
</IconColorProvider>
|
|
359
357
|
</ToggleStateContext.Provider>
|