panelui-native 0.7.0 → 0.12.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.
Files changed (161) hide show
  1. package/README.md +5 -3
  2. package/lib/module/components/attachment/index.js +219 -0
  3. package/lib/module/components/attachment/index.js.map +1 -0
  4. package/lib/module/components/bottom-sheet/index.js +53 -3
  5. package/lib/module/components/bottom-sheet/index.js.map +1 -1
  6. package/lib/module/components/button/index.js +19 -1
  7. package/lib/module/components/button/index.js.map +1 -1
  8. package/lib/module/components/dialog/index.js +6 -2
  9. package/lib/module/components/dialog/index.js.map +1 -1
  10. package/lib/module/components/empty-state/index.js +94 -17
  11. package/lib/module/components/empty-state/index.js.map +1 -1
  12. package/lib/module/components/frame/index.js +128 -15
  13. package/lib/module/components/frame/index.js.map +1 -1
  14. package/lib/module/components/input/index.js +107 -15
  15. package/lib/module/components/input/index.js.map +1 -1
  16. package/lib/module/components/line-chart/index.js +1024 -0
  17. package/lib/module/components/line-chart/index.js.map +1 -0
  18. package/lib/module/components/marker/index.js +190 -0
  19. package/lib/module/components/marker/index.js.map +1 -0
  20. package/lib/module/components/message/index.js +24 -8
  21. package/lib/module/components/message/index.js.map +1 -1
  22. package/lib/module/components/message-scroller/index.js +451 -0
  23. package/lib/module/components/message-scroller/index.js.map +1 -0
  24. package/lib/module/components/popover/index.js +511 -0
  25. package/lib/module/components/popover/index.js.map +1 -0
  26. package/lib/module/components/progress/index.js +52 -2
  27. package/lib/module/components/progress/index.js.map +1 -1
  28. package/lib/module/components/radio-group/index.js +132 -17
  29. package/lib/module/components/radio-group/index.js.map +1 -1
  30. package/lib/module/components/section-rail/index.js +355 -0
  31. package/lib/module/components/section-rail/index.js.map +1 -0
  32. package/lib/module/components/select/index.js +245 -56
  33. package/lib/module/components/select/index.js.map +1 -1
  34. package/lib/module/components/separator/index.js +91 -0
  35. package/lib/module/components/separator/index.js.map +1 -0
  36. package/lib/module/components/slider/index.js +355 -0
  37. package/lib/module/components/slider/index.js.map +1 -0
  38. package/lib/module/components/surface/index.js +21 -2
  39. package/lib/module/components/surface/index.js.map +1 -1
  40. package/lib/module/components/switch/index.js +10 -1
  41. package/lib/module/components/switch/index.js.map +1 -1
  42. package/lib/module/components/tabs/index.js +186 -16
  43. package/lib/module/components/tabs/index.js.map +1 -1
  44. package/lib/module/components/toggle-button/index.js +297 -0
  45. package/lib/module/components/toggle-button/index.js.map +1 -0
  46. package/lib/module/hooks/index.js +1 -0
  47. package/lib/module/hooks/index.js.map +1 -1
  48. package/lib/module/hooks/use-keyboard-avoidance.js +57 -8
  49. package/lib/module/hooks/use-keyboard-avoidance.js.map +1 -1
  50. package/lib/module/hooks/use-scroll-sections.js +89 -0
  51. package/lib/module/hooks/use-scroll-sections.js.map +1 -0
  52. package/lib/module/icons/index.js +69 -0
  53. package/lib/module/icons/index.js.map +1 -1
  54. package/lib/module/index.js +11 -2
  55. package/lib/module/index.js.map +1 -1
  56. package/lib/module/native/index.js.map +1 -1
  57. package/lib/module/primitives/keyboard-avoider.js +2 -0
  58. package/lib/module/primitives/keyboard-avoider.js.map +1 -1
  59. package/lib/module/primitives/scrim.js +72 -0
  60. package/lib/module/primitives/scrim.js.map +1 -0
  61. package/lib/module/utils/haptics.js +57 -0
  62. package/lib/module/utils/haptics.js.map +1 -0
  63. package/lib/typescript/src/components/attachment/index.d.ts +93 -0
  64. package/lib/typescript/src/components/attachment/index.d.ts.map +1 -0
  65. package/lib/typescript/src/components/bottom-sheet/index.d.ts +6 -1
  66. package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
  67. package/lib/typescript/src/components/button/index.d.ts +9 -4
  68. package/lib/typescript/src/components/button/index.d.ts.map +1 -1
  69. package/lib/typescript/src/components/dialog/index.d.ts +7 -1
  70. package/lib/typescript/src/components/dialog/index.d.ts.map +1 -1
  71. package/lib/typescript/src/components/empty-state/index.d.ts +152 -2
  72. package/lib/typescript/src/components/empty-state/index.d.ts.map +1 -1
  73. package/lib/typescript/src/components/frame/index.d.ts +47 -7
  74. package/lib/typescript/src/components/frame/index.d.ts.map +1 -1
  75. package/lib/typescript/src/components/input/index.d.ts +113 -2
  76. package/lib/typescript/src/components/input/index.d.ts.map +1 -1
  77. package/lib/typescript/src/components/line-chart/index.d.ts +247 -0
  78. package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -0
  79. package/lib/typescript/src/components/marker/index.d.ts +137 -0
  80. package/lib/typescript/src/components/marker/index.d.ts.map +1 -0
  81. package/lib/typescript/src/components/message/index.d.ts +8 -1
  82. package/lib/typescript/src/components/message/index.d.ts.map +1 -1
  83. package/lib/typescript/src/components/message-scroller/index.d.ts +150 -0
  84. package/lib/typescript/src/components/message-scroller/index.d.ts.map +1 -0
  85. package/lib/typescript/src/components/popover/index.d.ts +136 -0
  86. package/lib/typescript/src/components/popover/index.d.ts.map +1 -0
  87. package/lib/typescript/src/components/progress/index.d.ts +33 -0
  88. package/lib/typescript/src/components/progress/index.d.ts.map +1 -1
  89. package/lib/typescript/src/components/radio-group/index.d.ts +39 -0
  90. package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
  91. package/lib/typescript/src/components/section-rail/index.d.ts +115 -0
  92. package/lib/typescript/src/components/section-rail/index.d.ts.map +1 -0
  93. package/lib/typescript/src/components/select/index.d.ts +46 -7
  94. package/lib/typescript/src/components/select/index.d.ts.map +1 -1
  95. package/lib/typescript/src/components/separator/index.d.ts +47 -0
  96. package/lib/typescript/src/components/separator/index.d.ts.map +1 -0
  97. package/lib/typescript/src/components/slider/index.d.ts +203 -0
  98. package/lib/typescript/src/components/slider/index.d.ts.map +1 -0
  99. package/lib/typescript/src/components/surface/index.d.ts +51 -2
  100. package/lib/typescript/src/components/surface/index.d.ts.map +1 -1
  101. package/lib/typescript/src/components/switch/index.d.ts.map +1 -1
  102. package/lib/typescript/src/components/tabs/index.d.ts +46 -4
  103. package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
  104. package/lib/typescript/src/components/toggle-button/index.d.ts +244 -0
  105. package/lib/typescript/src/components/toggle-button/index.d.ts.map +1 -0
  106. package/lib/typescript/src/hooks/index.d.ts +1 -0
  107. package/lib/typescript/src/hooks/index.d.ts.map +1 -1
  108. package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts +10 -1
  109. package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts.map +1 -1
  110. package/lib/typescript/src/hooks/use-scroll-sections.d.ts +31 -0
  111. package/lib/typescript/src/hooks/use-scroll-sections.d.ts.map +1 -0
  112. package/lib/typescript/src/icons/index.d.ts +6 -0
  113. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  114. package/lib/typescript/src/index.d.ts +13 -4
  115. package/lib/typescript/src/index.d.ts.map +1 -1
  116. package/lib/typescript/src/native/index.d.ts +16 -1
  117. package/lib/typescript/src/native/index.d.ts.map +1 -1
  118. package/lib/typescript/src/primitives/keyboard-avoider.d.ts +8 -1
  119. package/lib/typescript/src/primitives/keyboard-avoider.d.ts.map +1 -1
  120. package/lib/typescript/src/primitives/scrim.d.ts +21 -0
  121. package/lib/typescript/src/primitives/scrim.d.ts.map +1 -0
  122. package/lib/typescript/src/utils/haptics.d.ts +12 -0
  123. package/lib/typescript/src/utils/haptics.d.ts.map +1 -0
  124. package/package.json +9 -1
  125. package/src/components/attachment/index.tsx +279 -0
  126. package/src/components/bottom-sheet/index.tsx +58 -2
  127. package/src/components/button/index.tsx +26 -5
  128. package/src/components/dialog/index.tsx +10 -1
  129. package/src/components/empty-state/index.tsx +60 -14
  130. package/src/components/frame/index.tsx +175 -29
  131. package/src/components/input/index.tsx +122 -16
  132. package/src/components/line-chart/index.tsx +1224 -0
  133. package/src/components/marker/index.tsx +194 -0
  134. package/src/components/message/index.tsx +29 -5
  135. package/src/components/message-scroller/index.tsx +574 -0
  136. package/src/components/popover/index.tsx +624 -0
  137. package/src/components/progress/index.tsx +81 -1
  138. package/src/components/radio-group/index.tsx +156 -18
  139. package/src/components/section-rail/index.tsx +459 -0
  140. package/src/components/select/index.tsx +265 -52
  141. package/src/components/separator/index.tsx +97 -0
  142. package/src/components/slider/index.tsx +371 -0
  143. package/src/components/surface/index.tsx +29 -3
  144. package/src/components/switch/index.tsx +8 -1
  145. package/src/components/tabs/index.tsx +208 -28
  146. package/src/components/toggle-button/index.tsx +374 -0
  147. package/src/hooks/index.ts +5 -0
  148. package/src/hooks/use-keyboard-avoidance.ts +73 -7
  149. package/src/hooks/use-scroll-sections.ts +127 -0
  150. package/src/icons/index.tsx +48 -0
  151. package/src/index.ts +97 -6
  152. package/src/native/index.ts +13 -1
  153. package/src/primitives/keyboard-avoider.tsx +9 -1
  154. package/src/primitives/scrim.tsx +95 -0
  155. package/src/utils/haptics.ts +63 -0
  156. package/theme.css +64 -2
  157. package/lib/module/components/inline-select/index.js +0 -284
  158. package/lib/module/components/inline-select/index.js.map +0 -1
  159. package/lib/typescript/src/components/inline-select/index.d.ts +0 -48
  160. package/lib/typescript/src/components/inline-select/index.d.ts.map +0 -1
  161. package/src/components/inline-select/index.tsx +0 -332
@@ -1,44 +1,93 @@
1
- import { forwardRef, useCallback, useState } from 'react';
1
+ /**
2
+ * Input — a text field with a label, a description and an error line.
3
+ *
4
+ * The field's focus state is animated rather than switched. A border that
5
+ * snaps between two colours reads as a redraw; one that crosses over in
6
+ * 150ms reads as the field responding to you. The interpolation runs on the
7
+ * UI thread, so it costs nothing and keeps up with a fast tab through a form.
8
+ *
9
+ * Two backgrounds, because a field's job changes with what surrounds it:
10
+ * `outline` sits on the page and draws its own edge; `filled` sits inside a
11
+ * card or a sheet, where a second border next to the container's own reads as
12
+ * a seam.
13
+ *
14
+ * ```tsx
15
+ * <Input label="Email" placeholder="you@example.com" />
16
+ * <Input variant="filled" size="lg" label="Name" isRequired />
17
+ * ```
18
+ *
19
+ * For a field with an icon or a button attached, reach for `InputGroup` — it
20
+ * measures its decorators and pads this field around them.
21
+ */
22
+ import { forwardRef, useCallback, useEffect, useState } from 'react';
2
23
  import { TextInput, View, type TextInputProps } from 'react-native';
3
- import { tv } from 'tailwind-variants';
24
+ import Animated, {
25
+ interpolateColor,
26
+ useAnimatedStyle,
27
+ useSharedValue,
28
+ withTiming,
29
+ } from 'react-native-reanimated';
30
+ import { tv, type VariantProps } from 'tailwind-variants';
4
31
  import { useCSSVariable } from 'uniwind';
5
32
  import { KeyboardAvoider } from '../../primitives/keyboard-avoider';
6
33
  import { Text } from '../../primitives/text';
34
+ import { Label } from '../label';
35
+
36
+ /** Long enough to read as a transition, short enough not to lag a fast tab. */
37
+ const FOCUS_DURATION = 150;
7
38
 
8
39
  const inputVariants = tv({
9
40
  slots: {
10
41
  container: 'w-full gap-1.5',
11
- field:
12
- 'h-11 w-full rounded-lg border border-input bg-background px-3 text-base text-foreground',
13
- label: 'text-sm font-medium text-foreground',
42
+ field: 'w-full rounded-lg border text-foreground',
14
43
  description: 'text-sm text-muted-foreground',
15
44
  error: 'text-sm text-destructive',
16
45
  },
17
46
  variants: {
18
- focused: {
19
- true: { field: 'border-ring' },
47
+ variant: {
48
+ // The border colour is animated, so it is deliberately not set here —
49
+ // only the background and the resting border width belong to the class.
50
+ outline: { field: 'bg-background' },
51
+ filled: { field: 'bg-muted' },
52
+ },
53
+ size: {
54
+ sm: { field: 'h-10 px-3 text-sm' },
55
+ md: { field: 'h-12 px-3.5 text-base' },
56
+ lg: { field: 'h-14 px-4 text-base' },
20
57
  },
21
- invalid: {
22
- true: { field: 'border-destructive/40' },
58
+ multiline: {
59
+ // A multiline field grows, so a fixed height would crop it. Text starts
60
+ // at the top rather than floating in the middle of an empty box.
61
+ true: { field: 'h-auto min-h-24 py-3' },
23
62
  },
24
63
  disabled: {
25
64
  true: { field: 'opacity-[0.64]' },
26
65
  },
27
66
  },
67
+ defaultVariants: {
68
+ variant: 'outline',
69
+ size: 'md',
70
+ },
28
71
  });
29
72
 
30
- export interface InputProps extends TextInputProps {
73
+ type InputVariantProps = VariantProps<typeof inputVariants>;
74
+
75
+ export interface InputProps
76
+ extends TextInputProps,
77
+ Omit<InputVariantProps, 'disabled' | 'multiline'> {
31
78
  className?: string;
32
79
  containerClassName?: string;
33
80
  label?: string;
34
81
  description?: string;
35
82
  /** Error message. When set, the field renders in its invalid state. */
36
83
  errorMessage?: string;
84
+ /** Marks the field required — an asterisk on the label, and the a11y state. */
85
+ isRequired?: boolean;
37
86
  disabled?: boolean;
38
87
  /**
39
88
  * Lift the field above the software keyboard when it would otherwise be
40
89
  * covered. Moves by exactly the overlap, and not at all when the field is
41
- * already clear.
90
+ * already clear — or when the keyboard belongs to a different field.
42
91
  *
43
92
  * Install `react-native-keyboard-controller` for this to behave on Android.
44
93
  *
@@ -50,6 +99,8 @@ export interface InputProps extends TextInputProps {
50
99
  keyboardOffset?: number;
51
100
  }
52
101
 
102
+ const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
103
+
53
104
  export const Input = forwardRef<TextInput, InputProps>(
54
105
  (
55
106
  {
@@ -58,23 +109,61 @@ export const Input = forwardRef<TextInput, InputProps>(
58
109
  label,
59
110
  description,
60
111
  errorMessage,
112
+ isRequired,
61
113
  disabled,
114
+ variant,
115
+ size,
62
116
  avoidKeyboard = false,
63
117
  keyboardOffset = 16,
64
118
  onFocus,
65
119
  onBlur,
120
+ style,
66
121
  ...props
67
122
  },
68
123
  ref
69
124
  ) => {
70
125
  const [focused, setFocused] = useState(false);
126
+ const invalid = !!errorMessage;
127
+
71
128
  const placeholderColor = useCSSVariable('--color-muted-foreground');
129
+ const restColor = useCSSVariable('--color-input');
130
+ const focusColor = useCSSVariable('--color-ring');
131
+ const errorColor = useCSSVariable('--color-destructive');
132
+
72
133
  const slots = inputVariants({
73
- focused,
74
- invalid: !!errorMessage,
134
+ variant,
135
+ size,
136
+ multiline: !!props.multiline,
75
137
  disabled: !!disabled,
76
138
  });
77
139
 
140
+ /*
141
+ * Border colour is driven by one 0..1 value rather than by a class per
142
+ * state. Uniwind can only swap a class wholesale, which is the snap this
143
+ * is here to avoid, and a shared value crosses between the two colours on
144
+ * the UI thread without a re-render.
145
+ */
146
+ const focus = useSharedValue(0);
147
+ useEffect(() => {
148
+ focus.value = withTiming(focused ? 1 : 0, { duration: FOCUS_DURATION });
149
+ }, [focused, focus]);
150
+
151
+ const resting = typeof restColor === 'string' ? restColor : '#e5e5e5';
152
+ const active = invalid
153
+ ? typeof errorColor === 'string'
154
+ ? errorColor
155
+ : '#ef4444'
156
+ : typeof focusColor === 'string'
157
+ ? focusColor
158
+ : '#a3a3a3';
159
+ // An invalid field is tinted even at rest — the error is a fact about the
160
+ // value, not about whether the field happens to be focused.
161
+ const idle = invalid ? active : resting;
162
+
163
+ const borderStyle = useAnimatedStyle(() => ({
164
+ borderColor: interpolateColor(focus.value, [0, 1], [idle, active]),
165
+ }));
166
+
78
167
  const handleFocus = useCallback<NonNullable<TextInputProps['onFocus']>>(
79
168
  (event) => {
80
169
  setFocused(true);
@@ -93,15 +182,24 @@ export const Input = forwardRef<TextInput, InputProps>(
93
182
 
94
183
  const body = (
95
184
  <>
96
- {label ? <Text className={slots.label()}>{label}</Text> : null}
97
- <TextInput
185
+ {label ? (
186
+ <Label isRequired={isRequired} isInvalid={invalid} isDisabled={!!disabled}>
187
+ {label}
188
+ </Label>
189
+ ) : null}
190
+ <AnimatedTextInput
98
191
  ref={ref}
99
192
  editable={!disabled}
100
193
  onFocus={handleFocus}
101
194
  onBlur={handleBlur}
102
195
  accessibilityLabel={label}
103
196
  accessibilityState={{ disabled: !!disabled }}
197
+ aria-required={isRequired}
198
+ aria-invalid={invalid}
104
199
  className={slots.field({ className })}
200
+ // Caller styles come last so InputGroup can pad the field around its
201
+ // decorators, but never last enough to drop the animated border.
202
+ style={[borderStyle, style]}
105
203
  placeholderTextColor={
106
204
  typeof placeholderColor === 'string' ? placeholderColor : undefined
107
205
  }
@@ -127,7 +225,15 @@ export const Input = forwardRef<TextInput, InputProps>(
127
225
  */
128
226
  if (avoidKeyboard) {
129
227
  return (
130
- <KeyboardAvoider offset={keyboardOffset} className={containerClasses}>
228
+ <KeyboardAvoider
229
+ // Only while *this* field is the one being typed into. Without it
230
+ // every avoiding field on the screen lifts the moment any field
231
+ // anywhere is tapped, and since they all aim at the same gap above
232
+ // the keyboard, they arrive stacked on top of one another.
233
+ active={focused}
234
+ offset={keyboardOffset}
235
+ className={containerClasses}
236
+ >
131
237
  {body}
132
238
  </KeyboardAvoider>
133
239
  );