@helpwave/hightide-native 0.5.0 → 0.7.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/package.json +3 -3
- package/src/components/card/Card.tsx +2 -4
- package/src/components/chat/ChatAttachmentMessageBubble.tsx +155 -94
- package/src/components/chat/ChatConversationList.tsx +4 -12
- package/src/components/chat/ChatConversationRow.tsx +159 -164
- package/src/components/chat/ChatDateDivider.tsx +3 -8
- package/src/components/chat/ChatMessageBubble.tsx +54 -52
- package/src/components/chat/ChatMessageComposer.tsx +4 -12
- package/src/components/chat/ChatMessageList.tsx +3 -9
- package/src/components/chat/ChatQuickReplyChip.tsx +41 -27
- package/src/components/chat/ChatSystemLine.tsx +4 -12
- package/src/components/chat/ChatThreadHeader.tsx +136 -101
- package/src/components/layout/Divider.tsx +2 -4
- package/src/components/list/ListActionItem.tsx +57 -65
- package/src/components/list/ListItem.tsx +8 -28
- package/src/components/list/ListItemTextContent.tsx +4 -2
- package/src/components/list/ListNavigationItem.tsx +55 -63
- package/src/components/user-interaction/Button.tsx +43 -49
- package/src/components/user-interaction/Checkbox.tsx +47 -48
- package/src/components/user-interaction/IconButton.tsx +40 -42
- package/src/components/user-interaction/Input.tsx +70 -67
- package/src/components/user-interaction/MultiSelect/MultiSelect.tsx +40 -0
- package/src/components/user-interaction/MultiSelect/MultiSelectContext.tsx +70 -0
- package/src/components/user-interaction/MultiSelect/MultiSelectMenu.tsx +87 -0
- package/src/components/user-interaction/MultiSelect/MultiSelectOption.tsx +87 -0
- package/src/components/user-interaction/MultiSelect/MultiSelectRoot.tsx +194 -0
- package/src/components/user-interaction/MultiSelect/MultiSelectTrigger.tsx +123 -0
- package/src/components/user-interaction/MultiSelect/index.ts +6 -0
- package/src/components/user-interaction/SearchBar.tsx +11 -17
- package/src/components/user-interaction/Select/Select.tsx +40 -0
- package/src/components/user-interaction/Select/SelectContext.tsx +69 -0
- package/src/components/user-interaction/Select/SelectMenu.tsx +87 -0
- package/src/components/user-interaction/Select/SelectOption.tsx +77 -0
- package/src/components/user-interaction/Select/SelectRoot.tsx +186 -0
- package/src/components/user-interaction/Select/SelectTrigger.tsx +92 -0
- package/src/components/user-interaction/Select/index.ts +6 -0
- package/src/components/user-interaction/Switch.tsx +46 -98
- package/src/components/user-interaction/Textarea.tsx +179 -0
- package/src/components/user-interaction/ThemedPressable.tsx +48 -49
- package/src/components/user-interaction/index.ts +1 -0
- package/src/components/visualization-and-display/Avatar.tsx +19 -17
- package/src/components/visualization-and-display/Chip.tsx +4 -12
- package/src/components/visualization-and-display/ThemedText.tsx +12 -5
- package/src/enums/chatMessageDirection.ts +18 -0
- package/src/enums/chatMessageStatus.ts +18 -0
- package/src/enums/hightideThemeModes.ts +18 -0
- package/src/enums/index.ts +7 -0
- package/src/enums/listItemContentOrder.ts +18 -0
- package/src/enums/themedTextAppearance.ts +18 -0
- package/src/enums/useMultiSelectFirstHighlightBehavior.ts +18 -0
- package/src/enums/useSelectFirstHighlightBehavior.ts +18 -0
- package/src/global-contexts/content-theme/ContentThemeProvider.tsx +9 -5
- package/src/global-contexts/theme/ThemeProvider.tsx +4 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useAnimatedStyleTransition.ts +460 -0
- package/src/hooks/useMemoizedTheme.ts +23 -0
- package/src/hooks/useMultiSelect.ts +3 -1
- package/src/hooks/useSelect.ts +3 -1
- package/src/icons/HightideIconRegistry.ts +2 -0
- package/src/theme/adapters/style-adapter-utils.ts +121 -27
- package/src/theme/resolvers/avatar.ts +48 -39
- package/src/theme/resolvers/button.ts +7 -17
- package/src/theme/resolvers/chat/attachment-message-bubble.ts +5 -6
- package/src/theme/resolvers/chat/conversation-row.ts +4 -6
- package/src/theme/resolvers/chat/message-bubble.ts +1 -2
- package/src/theme/resolvers/chat/message-composer.ts +1 -2
- package/src/theme/resolvers/chat/quick-reply-chip.ts +2 -3
- package/src/theme/resolvers/chat/system-line.ts +1 -2
- package/src/theme/resolvers/chat/thread-header.ts +3 -4
- package/src/theme/resolvers/checkbox.ts +2 -2
- package/src/theme/resolvers/chip.ts +1 -2
- package/src/theme/resolvers/iconButton.ts +4 -13
- package/src/theme/resolvers/index.ts +1 -0
- package/src/theme/resolvers/input.ts +30 -14
- package/src/theme/resolvers/listItem.ts +2 -3
- package/src/theme/resolvers/multiSelect.ts +1 -3
- package/src/theme/resolvers/searchBar.ts +1 -2
- package/src/theme/resolvers/select.ts +1 -2
- package/src/theme/resolvers/textarea.ts +14 -0
- package/src/theme/resolvers/themedPressable.ts +4 -15
- package/src/theme/themes/createHightideTheme.ts +6 -0
- package/src/theme/themes/hightideThemes.ts +3 -1
- package/src/theme/types/components/chat.ts +6 -21
- package/src/theme/types/components/hightide.ts +2 -0
- package/src/theme/types/components/index.ts +1 -0
- package/src/theme/types/components/input.ts +3 -0
- package/src/theme/types/components/textarea.ts +9 -0
- package/src/theme/types/resolver.ts +4 -27
- package/src/utils/index.ts +1 -0
- package/src/utils/pressableInteraction.ts +7 -0
- package/src/components/user-interaction/MultiSelect.tsx +0 -281
- package/src/components/user-interaction/Select.tsx +0 -242
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useLayoutEffect,
|
|
3
|
+
useMemo,
|
|
4
|
+
useRef,
|
|
5
|
+
useState
|
|
6
|
+
} from 'react'
|
|
7
|
+
import {
|
|
8
|
+
Animated,
|
|
9
|
+
StyleSheet,
|
|
10
|
+
type ViewStyle
|
|
11
|
+
} from 'react-native'
|
|
12
|
+
|
|
13
|
+
import { HexColorUtils } from '../utils/hex'
|
|
14
|
+
|
|
15
|
+
const TRANSFORM_KEYS = [
|
|
16
|
+
'translateX',
|
|
17
|
+
'translateY',
|
|
18
|
+
'scale',
|
|
19
|
+
'scaleX',
|
|
20
|
+
'scaleY',
|
|
21
|
+
'rotate',
|
|
22
|
+
'rotateX',
|
|
23
|
+
'rotateY',
|
|
24
|
+
'rotateZ',
|
|
25
|
+
'skewX',
|
|
26
|
+
'skewY',
|
|
27
|
+
'perspective',
|
|
28
|
+
] as const
|
|
29
|
+
|
|
30
|
+
type TransformKey = typeof TRANSFORM_KEYS[number]
|
|
31
|
+
type StyleRecord = Record<string, unknown>
|
|
32
|
+
type TransformEntry = Record<string, unknown>
|
|
33
|
+
type AnimatedViewStyle = Animated.WithAnimatedObject<ViewStyle>
|
|
34
|
+
type AnimatedAttribute = keyof ViewStyle
|
|
35
|
+
|
|
36
|
+
const POSITION_STYLE_KEYS = [
|
|
37
|
+
'position',
|
|
38
|
+
'left',
|
|
39
|
+
'right',
|
|
40
|
+
'top',
|
|
41
|
+
'bottom',
|
|
42
|
+
] as const satisfies readonly AnimatedAttribute[]
|
|
43
|
+
|
|
44
|
+
const toRecord = (style: ViewStyle): StyleRecord => style as StyleRecord
|
|
45
|
+
|
|
46
|
+
const resolveAnimatedAttributes = (
|
|
47
|
+
animatedAttributes: AnimatedAttribute[]
|
|
48
|
+
): AnimatedAttribute[] => {
|
|
49
|
+
const resolved: AnimatedAttribute[] = []
|
|
50
|
+
|
|
51
|
+
for (const key of animatedAttributes) {
|
|
52
|
+
if (key === 'position') {
|
|
53
|
+
resolved.push(...POSITION_STYLE_KEYS)
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
resolved.push(key)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return [...new Set(resolved)]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const parseUnitValue = (value: unknown, unit: string): number | undefined => {
|
|
64
|
+
if (typeof value !== 'string' || !value.endsWith(unit)) {
|
|
65
|
+
return undefined
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const parsed = Number.parseFloat(value.slice(0, -unit.length))
|
|
69
|
+
return Number.isFinite(parsed) ? parsed : undefined
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const parseHexChannels = (hex: string): [number, number, number, number] => {
|
|
73
|
+
const normalized = hex.startsWith('#') ? hex.slice(1) : hex
|
|
74
|
+
const rgb = normalized.slice(0, 6)
|
|
75
|
+
const alphaHex = normalized.length >= 8 ? normalized.slice(6, 8) : 'ff'
|
|
76
|
+
|
|
77
|
+
return [
|
|
78
|
+
Number.parseInt(rgb.slice(0, 2), 16),
|
|
79
|
+
Number.parseInt(rgb.slice(2, 4), 16),
|
|
80
|
+
Number.parseInt(rgb.slice(4, 6), 16),
|
|
81
|
+
Number.parseInt(alphaHex, 16) / 255,
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const toHexColor = (r: number, g: number, b: number, alpha: number): string => {
|
|
86
|
+
const channel = (value: number) => Math.round(Math.min(255, Math.max(0, value)))
|
|
87
|
+
.toString(16)
|
|
88
|
+
.padStart(2, '0')
|
|
89
|
+
const rgb = `#${channel(r)}${channel(g)}${channel(b)}`
|
|
90
|
+
|
|
91
|
+
if (alpha >= 1) {
|
|
92
|
+
return rgb
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return `${rgb}${channel(alpha * 255)}`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const mixNumber = (from: number, to: number, progress: number): number => (
|
|
99
|
+
from + (to - from) * progress
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
const mixColor = (from: string, to: string, progress: number): string | undefined => {
|
|
103
|
+
const fromHex = HexColorUtils.tryParseColorValue(from)
|
|
104
|
+
const toHex = HexColorUtils.tryParseColorValue(to)
|
|
105
|
+
|
|
106
|
+
if (fromHex === undefined || toHex === undefined) {
|
|
107
|
+
return undefined
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const [fr, fg, fb, fa] = parseHexChannels(fromHex)
|
|
111
|
+
const [tr, tg, tb, ta] = parseHexChannels(toHex)
|
|
112
|
+
|
|
113
|
+
return toHexColor(
|
|
114
|
+
mixNumber(fr, tr, progress),
|
|
115
|
+
mixNumber(fg, tg, progress),
|
|
116
|
+
mixNumber(fb, tb, progress),
|
|
117
|
+
mixNumber(fa, ta, progress)
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const mixScalar = (from: unknown, to: unknown, progress: number): unknown => {
|
|
122
|
+
if (from === to) {
|
|
123
|
+
return to
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (typeof from === 'number' && typeof to === 'number') {
|
|
127
|
+
return mixNumber(from, to, progress)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const fromPercent = parseUnitValue(from, '%')
|
|
131
|
+
const toPercent = parseUnitValue(to, '%')
|
|
132
|
+
if (fromPercent !== undefined && toPercent !== undefined) {
|
|
133
|
+
return `${mixNumber(fromPercent, toPercent, progress)}%`
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const fromDeg = parseUnitValue(from, 'deg')
|
|
137
|
+
const toDeg = parseUnitValue(to, 'deg')
|
|
138
|
+
if (fromDeg !== undefined && toDeg !== undefined) {
|
|
139
|
+
return `${mixNumber(fromDeg, toDeg, progress)}deg`
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (typeof from === 'string' && typeof to === 'string') {
|
|
143
|
+
const mixedColor = mixColor(from, to, progress)
|
|
144
|
+
if (mixedColor !== undefined) {
|
|
145
|
+
return mixedColor
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return progress < 1 ? from : to
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const flattenTransform = (transform: ViewStyle['transform']): TransformEntry => {
|
|
153
|
+
if (!Array.isArray(transform)) {
|
|
154
|
+
return {}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const flattened: TransformEntry = {}
|
|
158
|
+
|
|
159
|
+
for (const entry of transform) {
|
|
160
|
+
if (entry !== undefined && typeof entry === 'object') {
|
|
161
|
+
Object.assign(flattened, entry)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return flattened
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const transformKeys = (from: TransformEntry, to: TransformEntry): TransformKey[] => {
|
|
169
|
+
const keys = TRANSFORM_KEYS.filter((key) => from[key] !== undefined || to[key] !== undefined)
|
|
170
|
+
|
|
171
|
+
for (const key of Object.keys({ ...from, ...to })) {
|
|
172
|
+
if (!TRANSFORM_KEYS.includes(key as TransformKey)) {
|
|
173
|
+
keys.push(key as TransformKey)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return keys
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const mixTransforms = (
|
|
181
|
+
from: ViewStyle['transform'],
|
|
182
|
+
to: ViewStyle['transform'],
|
|
183
|
+
progress: number
|
|
184
|
+
): ViewStyle['transform'] => {
|
|
185
|
+
const fromFlat = flattenTransform(from)
|
|
186
|
+
const toFlat = flattenTransform(to)
|
|
187
|
+
const mixed: TransformEntry[] = []
|
|
188
|
+
|
|
189
|
+
for (const key of transformKeys(fromFlat, toFlat)) {
|
|
190
|
+
const fromValue = fromFlat[key]
|
|
191
|
+
const toValue = toFlat[key]
|
|
192
|
+
|
|
193
|
+
if (toValue === undefined) {
|
|
194
|
+
continue
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
mixed.push({
|
|
198
|
+
[key]: fromValue === undefined ? toValue : mixScalar(fromValue, toValue, progress),
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return mixed.length === 0 ? undefined : mixed as unknown as ViewStyle['transform']
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const mixStyles = (
|
|
206
|
+
from: ViewStyle,
|
|
207
|
+
to: ViewStyle,
|
|
208
|
+
progress: number,
|
|
209
|
+
animatedAttributes: (keyof ViewStyle)[]
|
|
210
|
+
): ViewStyle => {
|
|
211
|
+
const fromRecord = toRecord(from)
|
|
212
|
+
const toRecordStyle = toRecord(to)
|
|
213
|
+
const attributes = resolveAnimatedAttributes(animatedAttributes)
|
|
214
|
+
const mixed: StyleRecord = { ...toRecordStyle }
|
|
215
|
+
|
|
216
|
+
for (const key of attributes) {
|
|
217
|
+
if (key === 'transform') {
|
|
218
|
+
mixed.transform = mixTransforms(from.transform, to.transform, progress)
|
|
219
|
+
continue
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const fromValue = fromRecord[key]
|
|
223
|
+
const toValue = toRecordStyle[key]
|
|
224
|
+
|
|
225
|
+
if (fromValue === undefined || fromValue === toValue) {
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
mixed[key] = mixScalar(fromValue, toValue, progress)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return mixed as ViewStyle
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const interpolateScalar = (
|
|
236
|
+
from: unknown,
|
|
237
|
+
to: unknown,
|
|
238
|
+
progress: Animated.Value
|
|
239
|
+
): unknown => {
|
|
240
|
+
if (from === to) {
|
|
241
|
+
return to
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (typeof from === 'number' && typeof to === 'number') {
|
|
245
|
+
return progress.interpolate({
|
|
246
|
+
inputRange: [0, 1],
|
|
247
|
+
outputRange: [from, to],
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const fromPercent = parseUnitValue(from, '%')
|
|
252
|
+
const toPercent = parseUnitValue(to, '%')
|
|
253
|
+
if (fromPercent !== undefined && toPercent !== undefined) {
|
|
254
|
+
return progress.interpolate({
|
|
255
|
+
inputRange: [0, 1],
|
|
256
|
+
outputRange: [`${fromPercent}%`, `${toPercent}%`],
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const fromDeg = parseUnitValue(from, 'deg')
|
|
261
|
+
const toDeg = parseUnitValue(to, 'deg')
|
|
262
|
+
if (fromDeg !== undefined && toDeg !== undefined) {
|
|
263
|
+
return progress.interpolate({
|
|
264
|
+
inputRange: [0, 1],
|
|
265
|
+
outputRange: [`${fromDeg}deg`, `${toDeg}deg`],
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (typeof from === 'string' && typeof to === 'string') {
|
|
270
|
+
const fromColor = HexColorUtils.tryParseColorValue(from)
|
|
271
|
+
const toColor = HexColorUtils.tryParseColorValue(to)
|
|
272
|
+
|
|
273
|
+
if (fromColor !== undefined && toColor !== undefined) {
|
|
274
|
+
return progress.interpolate({
|
|
275
|
+
inputRange: [0, 1],
|
|
276
|
+
outputRange: [fromColor, toColor],
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return to
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const interpolateTransforms = (
|
|
285
|
+
from: ViewStyle['transform'],
|
|
286
|
+
to: ViewStyle['transform'],
|
|
287
|
+
progress: Animated.Value
|
|
288
|
+
): ViewStyle['transform'] | Animated.WithAnimatedValue<ViewStyle['transform']> => {
|
|
289
|
+
const fromFlat = flattenTransform(from)
|
|
290
|
+
const toFlat = flattenTransform(to)
|
|
291
|
+
const interpolated: TransformEntry[] = []
|
|
292
|
+
|
|
293
|
+
for (const key of transformKeys(fromFlat, toFlat)) {
|
|
294
|
+
const fromValue = fromFlat[key]
|
|
295
|
+
const toValue = toFlat[key]
|
|
296
|
+
|
|
297
|
+
if (toValue === undefined) {
|
|
298
|
+
continue
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
interpolated.push({
|
|
302
|
+
[key]: fromValue === undefined ? toValue : interpolateScalar(fromValue, toValue, progress),
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return interpolated.length === 0 ? undefined : interpolated as unknown as ViewStyle['transform']
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const createInterpolatedStyle = (
|
|
310
|
+
from: ViewStyle,
|
|
311
|
+
to: ViewStyle,
|
|
312
|
+
progress: Animated.Value,
|
|
313
|
+
animatedAttributes: (keyof ViewStyle)[]
|
|
314
|
+
): AnimatedViewStyle => {
|
|
315
|
+
const fromRecord = toRecord(from)
|
|
316
|
+
const toRecordStyle = toRecord(to)
|
|
317
|
+
const attributes = resolveAnimatedAttributes(animatedAttributes)
|
|
318
|
+
const interpolated: StyleRecord = { ...toRecordStyle }
|
|
319
|
+
|
|
320
|
+
for (const key of attributes) {
|
|
321
|
+
if (key === 'transform') {
|
|
322
|
+
interpolated.transform = interpolateTransforms(from.transform, to.transform, progress)
|
|
323
|
+
continue
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const fromValue = fromRecord[key]
|
|
327
|
+
const toValue = toRecordStyle[key]
|
|
328
|
+
|
|
329
|
+
if (fromValue === undefined || fromValue === toValue) {
|
|
330
|
+
continue
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
interpolated[key] = interpolateScalar(fromValue, toValue, progress)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return interpolated as AnimatedViewStyle
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const pickAnimatedAttributes = (
|
|
340
|
+
style: ViewStyle,
|
|
341
|
+
animatedAttributes: (keyof ViewStyle)[]
|
|
342
|
+
): StyleRecord => {
|
|
343
|
+
const flattened = StyleSheet.flatten(style) ?? {}
|
|
344
|
+
const record = toRecord(flattened)
|
|
345
|
+
const picked: StyleRecord = {}
|
|
346
|
+
const attributes = resolveAnimatedAttributes(animatedAttributes)
|
|
347
|
+
|
|
348
|
+
for (const key of attributes) {
|
|
349
|
+
picked[key] = record[key]
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return picked
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const areAnimatedAttributesEqual = (
|
|
356
|
+
left: ViewStyle,
|
|
357
|
+
right: ViewStyle,
|
|
358
|
+
animatedAttributes: (keyof ViewStyle)[]
|
|
359
|
+
): boolean => (
|
|
360
|
+
JSON.stringify(pickAnimatedAttributes(left, animatedAttributes))
|
|
361
|
+
=== JSON.stringify(pickAnimatedAttributes(right, animatedAttributes))
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
const areStylesEqual = (left: ViewStyle, right: ViewStyle): boolean => {
|
|
365
|
+
const normalize = (style: ViewStyle): string => {
|
|
366
|
+
const flattened = StyleSheet.flatten(style) ?? {}
|
|
367
|
+
const record = toRecord(flattened)
|
|
368
|
+
const normalized: StyleRecord = {}
|
|
369
|
+
|
|
370
|
+
for (const key of Object.keys(record).sort()) {
|
|
371
|
+
normalized[key] = record[key]
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return JSON.stringify(normalized)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return normalize(left) === normalize(right)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export type AnimatedStyleTransitionProps = {
|
|
381
|
+
style: ViewStyle,
|
|
382
|
+
duration: number,
|
|
383
|
+
animatedAttributes: (keyof ViewStyle)[],
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// TODO fix transitions
|
|
387
|
+
export const useAnimatedStyleTransition = ({
|
|
388
|
+
style,
|
|
389
|
+
duration,
|
|
390
|
+
animatedAttributes,
|
|
391
|
+
}: AnimatedStyleTransitionProps): AnimatedViewStyle => {
|
|
392
|
+
const progress = useRef(new Animated.Value(1)).current
|
|
393
|
+
const fromRef = useRef(style)
|
|
394
|
+
const toRef = useRef(style)
|
|
395
|
+
const isFirstRender = useRef(true)
|
|
396
|
+
const animationRef = useRef<Animated.CompositeAnimation | null>(null)
|
|
397
|
+
const [transition, setTransition] = useState({
|
|
398
|
+
from: style,
|
|
399
|
+
to: style,
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
useLayoutEffect(() => {
|
|
403
|
+
if (isFirstRender.current) {
|
|
404
|
+
isFirstRender.current = false
|
|
405
|
+
fromRef.current = style
|
|
406
|
+
toRef.current = style
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (areAnimatedAttributesEqual(toRef.current, style, animatedAttributes)) {
|
|
411
|
+
if (areStylesEqual(toRef.current, style)) {
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
toRef.current = style
|
|
416
|
+
setTransition((current) => ({
|
|
417
|
+
from: current.from,
|
|
418
|
+
to: style,
|
|
419
|
+
}))
|
|
420
|
+
return
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
progress.stopAnimation((currentProgress) => {
|
|
424
|
+
const captured = mixStyles(
|
|
425
|
+
fromRef.current,
|
|
426
|
+
toRef.current,
|
|
427
|
+
currentProgress,
|
|
428
|
+
animatedAttributes
|
|
429
|
+
)
|
|
430
|
+
fromRef.current = captured
|
|
431
|
+
toRef.current = style
|
|
432
|
+
setTransition({
|
|
433
|
+
from: captured,
|
|
434
|
+
to: style,
|
|
435
|
+
})
|
|
436
|
+
animationRef.current?.stop()
|
|
437
|
+
progress.setValue(0)
|
|
438
|
+
animationRef.current = Animated.timing(progress, {
|
|
439
|
+
toValue: 1,
|
|
440
|
+
duration,
|
|
441
|
+
useNativeDriver: false,
|
|
442
|
+
})
|
|
443
|
+
animationRef.current.start()
|
|
444
|
+
})
|
|
445
|
+
}, [animatedAttributes, duration, progress, style])
|
|
446
|
+
|
|
447
|
+
useLayoutEffect(() => () => {
|
|
448
|
+
animationRef.current?.stop()
|
|
449
|
+
}, [])
|
|
450
|
+
|
|
451
|
+
return useMemo(
|
|
452
|
+
() => createInterpolatedStyle(
|
|
453
|
+
transition.from,
|
|
454
|
+
transition.to,
|
|
455
|
+
progress,
|
|
456
|
+
animatedAttributes
|
|
457
|
+
),
|
|
458
|
+
[animatedAttributes, progress, transition]
|
|
459
|
+
)
|
|
460
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { StyleSheet } from 'react-native'
|
|
3
|
+
import type { StyleOverwrite, StyleResolverFunction } from '../theme'
|
|
4
|
+
|
|
5
|
+
export const useMemoizedTheme = <State, Style>(
|
|
6
|
+
resolver: StyleResolverFunction<State, Style>,
|
|
7
|
+
state: State,
|
|
8
|
+
overwrite?: StyleOverwrite<State, Style>
|
|
9
|
+
): Style => {
|
|
10
|
+
const memoizedTheme = useMemo(() => StyleSheet.flatten(resolver(state, overwrite)), [overwrite, resolver, state])
|
|
11
|
+
return memoizedTheme as Style
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const useMemoizedThemeFactory = <State, Factory>(
|
|
15
|
+
resolver: StyleResolverFunction<State, Factory>,
|
|
16
|
+
state: State,
|
|
17
|
+
overwrite?: StyleOverwrite<State, Factory>
|
|
18
|
+
): Factory => {
|
|
19
|
+
return useMemo(
|
|
20
|
+
() => resolver(state, overwrite) as Factory,
|
|
21
|
+
[overwrite, resolver, state]
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -30,7 +30,9 @@ export interface UseMultiSelectOptions {
|
|
|
30
30
|
typeAheadResetMs?: number,
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
import type { UseMultiSelectFirstHighlightBehavior } from '../enums/useMultiSelectFirstHighlightBehavior'
|
|
34
|
+
|
|
35
|
+
export type { UseMultiSelectFirstHighlightBehavior }
|
|
34
36
|
|
|
35
37
|
export interface UseMultiSelectState {
|
|
36
38
|
value: string[],
|
package/src/hooks/useSelect.ts
CHANGED
|
@@ -31,7 +31,9 @@ export interface UseSelectOptions {
|
|
|
31
31
|
typeAheadResetMs?: number,
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
import type { UseSelectFirstHighlightBehavior } from '../enums/useSelectFirstHighlightBehavior'
|
|
35
|
+
|
|
36
|
+
export type { UseSelectFirstHighlightBehavior }
|
|
35
37
|
|
|
36
38
|
export interface UseSelectState {
|
|
37
39
|
value: string | null,
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
CheckCheck,
|
|
4
4
|
ChevronDown,
|
|
5
5
|
ChevronRight,
|
|
6
|
+
Clock,
|
|
6
7
|
Download,
|
|
7
8
|
FileText,
|
|
8
9
|
Minus,
|
|
@@ -20,6 +21,7 @@ export const HightideIconRegistry = {
|
|
|
20
21
|
CheckCheck: CheckCheck,
|
|
21
22
|
ChevronDown: ChevronDown,
|
|
22
23
|
ChevronRight: ChevronRight,
|
|
24
|
+
Clock: Clock,
|
|
23
25
|
Download: Download,
|
|
24
26
|
FileText: FileText,
|
|
25
27
|
Minus: Minus,
|