@helpwave/hightide-native 0.0.1 → 0.0.2
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 +56 -0
- package/package.json +3 -3
- package/src/components/chat/ChatAttachmentCard.tsx +97 -0
- package/src/components/chat/ChatConversationList.tsx +68 -0
- package/src/components/chat/ChatConversationRow.tsx +145 -0
- package/src/components/chat/ChatDateDivider.tsx +45 -0
- package/src/components/chat/ChatMessageBubble.tsx +102 -0
- package/src/components/chat/ChatMessageCard.tsx +107 -0
- package/src/components/chat/ChatMessageComposer.tsx +110 -0
- package/src/components/chat/ChatMessageList.tsx +59 -0
- package/src/components/chat/ChatQuickReplyChip.tsx +71 -0
- package/src/components/chat/ChatSystemLine.tsx +57 -0
- package/src/components/chat/ChatThreadHeader.tsx +80 -0
- package/src/components/chat/index.ts +11 -0
- package/src/components/index.ts +4 -7
- package/src/components/menu/Menu.tsx +51 -0
- package/src/components/menu/MenuActionItem.tsx +85 -0
- package/src/components/menu/MenuItem.tsx +63 -0
- package/src/components/menu/MenuNavigationItem.tsx +85 -0
- package/src/components/menu/index.ts +4 -0
- package/src/components/{Button → user-interaction}/Button.tsx +22 -29
- package/src/components/{Checkbox → user-interaction}/Checkbox.tsx +14 -11
- package/src/components/{IconButton → user-interaction}/IconButton.tsx +17 -25
- package/src/components/{Input → user-interaction}/Input.tsx +15 -11
- package/src/components/{MultiSelect → user-interaction}/MultiSelect.tsx +34 -9
- package/src/components/{Select → user-interaction}/Select.tsx +33 -8
- package/src/components/user-interaction/index.ts +6 -0
- package/src/components/{Chip → visualization-and-display}/Chip.tsx +17 -13
- package/src/theme/resolvers/button.ts +7 -5
- package/src/theme/resolvers/chat.ts +384 -0
- package/src/theme/resolvers/checkbox.ts +5 -4
- package/src/theme/resolvers/chip.ts +7 -5
- package/src/theme/resolvers/iconButton.ts +6 -4
- package/src/theme/resolvers/index.ts +2 -0
- package/src/theme/resolvers/input.ts +6 -4
- package/src/theme/resolvers/menu.ts +112 -0
- package/src/theme/resolvers/multiSelect.ts +20 -19
- package/src/theme/resolvers/select.ts +16 -15
- package/src/theme/themes/createTheme.ts +4 -0
- package/src/theme/types/button.ts +7 -30
- package/src/theme/types/chat.ts +141 -0
- package/src/theme/types/checkbox.ts +5 -14
- package/src/theme/types/chip.ts +7 -30
- package/src/theme/types/components.ts +4 -0
- package/src/theme/types/iconButton.ts +6 -20
- package/src/theme/types/index.ts +2 -0
- package/src/theme/types/input.ts +5 -15
- package/src/theme/types/menu.ts +50 -0
- package/src/theme/types/multiSelect.ts +17 -39
- package/src/theme/types/resolver.ts +43 -1
- package/src/theme/types/select.ts +18 -55
- package/src/components/Button/index.ts +0 -1
- package/src/components/Checkbox/index.ts +0 -1
- package/src/components/IconButton/index.ts +0 -1
- package/src/components/Input/index.ts +0 -1
- package/src/components/MultiSelect/index.ts +0 -1
- package/src/components/Select/index.ts +0 -1
- /package/src/components/{Chip → visualization-and-display}/index.ts +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { useMemo, type ReactNode } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
TextInput,
|
|
4
|
+
View,
|
|
5
|
+
type StyleProp,
|
|
6
|
+
type ViewProps,
|
|
7
|
+
type ViewStyle
|
|
8
|
+
} from 'react-native'
|
|
9
|
+
import { SendHorizontal } from 'lucide-react-native'
|
|
10
|
+
import { useControlledState } from '@helpwave/hightide-utils/hooks'
|
|
11
|
+
import { IconButton } from '../user-interaction/IconButton'
|
|
12
|
+
import { useTheme } from '../../global-contexts/theme'
|
|
13
|
+
import type {
|
|
14
|
+
ChatMessageComposerInputStyle,
|
|
15
|
+
ChatMessageComposerStyle,
|
|
16
|
+
StyleOverwrite
|
|
17
|
+
} from '../../theme'
|
|
18
|
+
|
|
19
|
+
export type ChatMessageComposerProps = Omit<ViewProps, 'style'> & {
|
|
20
|
+
value?: string,
|
|
21
|
+
initialValue?: string,
|
|
22
|
+
onValueChange?: (value: string) => void,
|
|
23
|
+
onSend: (value: string) => void,
|
|
24
|
+
placeholder?: string,
|
|
25
|
+
sendLabel?: string,
|
|
26
|
+
disabled?: boolean,
|
|
27
|
+
actions?: ReactNode,
|
|
28
|
+
trailing?: ReactNode,
|
|
29
|
+
style?: StyleProp<ViewStyle>,
|
|
30
|
+
composerStyle?: StyleOverwrite<Record<string, never>, ChatMessageComposerStyle>,
|
|
31
|
+
inputStyle?: StyleOverwrite<Record<string, never>, ChatMessageComposerInputStyle>,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const ChatMessageComposer = ({
|
|
35
|
+
value: controlledValue,
|
|
36
|
+
initialValue,
|
|
37
|
+
onValueChange,
|
|
38
|
+
onSend,
|
|
39
|
+
placeholder,
|
|
40
|
+
sendLabel = 'Send',
|
|
41
|
+
disabled = false,
|
|
42
|
+
actions,
|
|
43
|
+
trailing,
|
|
44
|
+
style,
|
|
45
|
+
composerStyle,
|
|
46
|
+
inputStyle,
|
|
47
|
+
...props
|
|
48
|
+
}: ChatMessageComposerProps) => {
|
|
49
|
+
const { theme } = useTheme()
|
|
50
|
+
const [value, setValue] = useControlledState({
|
|
51
|
+
value: controlledValue,
|
|
52
|
+
onValueChange,
|
|
53
|
+
defaultValue: initialValue ?? '',
|
|
54
|
+
})
|
|
55
|
+
const state = useMemo(() => ({}), [])
|
|
56
|
+
|
|
57
|
+
const resolvedComposerStyle = useMemo(
|
|
58
|
+
() => theme.components.chat.messageComposer(state, composerStyle),
|
|
59
|
+
[theme, state, composerStyle]
|
|
60
|
+
)
|
|
61
|
+
const resolvedInputStyle = useMemo(
|
|
62
|
+
() => theme.components.chat.messageComposerInput(state, inputStyle),
|
|
63
|
+
[theme, state, inputStyle]
|
|
64
|
+
)
|
|
65
|
+
const placeholderColor = useMemo(
|
|
66
|
+
() => theme.components.chat.messageComposerPlaceholderColor(state),
|
|
67
|
+
[theme, state]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const send = () => {
|
|
71
|
+
const trimmed = (value ?? '').trim()
|
|
72
|
+
if (!trimmed || disabled) {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
onSend(trimmed)
|
|
76
|
+
setValue('')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<View {...props} style={[resolvedComposerStyle, style]}>
|
|
81
|
+
{actions != null && (
|
|
82
|
+
<View style={{ flexDirection: 'row', alignItems: 'flex-start' }}>
|
|
83
|
+
{actions}
|
|
84
|
+
</View>
|
|
85
|
+
)}
|
|
86
|
+
<TextInput
|
|
87
|
+
value={value ?? ''}
|
|
88
|
+
onChangeText={setValue}
|
|
89
|
+
placeholder={placeholder}
|
|
90
|
+
placeholderTextColor={placeholderColor}
|
|
91
|
+
editable={!disabled}
|
|
92
|
+
multiline
|
|
93
|
+
style={resolvedInputStyle}
|
|
94
|
+
onSubmitEditing={send}
|
|
95
|
+
returnKeyType="send"
|
|
96
|
+
/>
|
|
97
|
+
{trailing}
|
|
98
|
+
<IconButton
|
|
99
|
+
accessibilityLabel={sendLabel}
|
|
100
|
+
color="primary"
|
|
101
|
+
coloringStyle="solid"
|
|
102
|
+
disabled={disabled || !(value ?? '').trim()}
|
|
103
|
+
size="md"
|
|
104
|
+
onPress={send}
|
|
105
|
+
icon={SendHorizontal}
|
|
106
|
+
buttonStyle={(prev) => [prev, { borderRadius: 999 }]}
|
|
107
|
+
/>
|
|
108
|
+
</View>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, type ReactNode } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
ScrollView,
|
|
4
|
+
type NativeScrollEvent,
|
|
5
|
+
type NativeSyntheticEvent,
|
|
6
|
+
type StyleProp,
|
|
7
|
+
type ViewStyle
|
|
8
|
+
} from 'react-native'
|
|
9
|
+
import { useTheme } from '../../global-contexts/theme'
|
|
10
|
+
import type { ChatMessageListStyle, StyleOverwrite } from '../../theme'
|
|
11
|
+
|
|
12
|
+
export type ChatMessageListProps = {
|
|
13
|
+
autoScroll?: boolean,
|
|
14
|
+
children?: ReactNode,
|
|
15
|
+
style?: StyleProp<ViewStyle>,
|
|
16
|
+
listStyle?: StyleOverwrite<Record<string, never>, ChatMessageListStyle>,
|
|
17
|
+
contentContainerStyle?: StyleProp<ViewStyle>,
|
|
18
|
+
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const ChatMessageList = ({
|
|
22
|
+
autoScroll = true,
|
|
23
|
+
children,
|
|
24
|
+
style,
|
|
25
|
+
listStyle,
|
|
26
|
+
contentContainerStyle,
|
|
27
|
+
onScroll,
|
|
28
|
+
}: ChatMessageListProps) => {
|
|
29
|
+
const { theme } = useTheme()
|
|
30
|
+
const scrollRef = useRef<ScrollView>(null)
|
|
31
|
+
const state = useMemo(() => ({}), [])
|
|
32
|
+
|
|
33
|
+
const resolvedListStyle = useMemo(
|
|
34
|
+
() => theme.components.chat.messageList(state, listStyle),
|
|
35
|
+
[theme, state, listStyle]
|
|
36
|
+
)
|
|
37
|
+
const listGap = useMemo(
|
|
38
|
+
() => (theme.components.chat.messageList(state) as ViewStyle).gap,
|
|
39
|
+
[theme, state]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (autoScroll) {
|
|
44
|
+
scrollRef.current?.scrollToEnd({ animated: true })
|
|
45
|
+
}
|
|
46
|
+
}, [autoScroll, children])
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<ScrollView
|
|
50
|
+
ref={scrollRef}
|
|
51
|
+
style={[resolvedListStyle, style]}
|
|
52
|
+
contentContainerStyle={[{ gap: listGap }, contentContainerStyle]}
|
|
53
|
+
onScroll={onScroll}
|
|
54
|
+
scrollEventThrottle={16}
|
|
55
|
+
>
|
|
56
|
+
{children}
|
|
57
|
+
</ScrollView>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
Text,
|
|
5
|
+
type PressableProps,
|
|
6
|
+
type StyleProp,
|
|
7
|
+
type ViewStyle
|
|
8
|
+
} from 'react-native'
|
|
9
|
+
import { useTheme } from '../../global-contexts/theme'
|
|
10
|
+
import type {
|
|
11
|
+
ChatQuickReplyChipState,
|
|
12
|
+
ChatQuickReplyChipStyle,
|
|
13
|
+
ChatQuickReplyChipTextStyle,
|
|
14
|
+
StyleOverwrite
|
|
15
|
+
} from '../../theme'
|
|
16
|
+
|
|
17
|
+
export type ChatQuickReplyChipProps = Omit<PressableProps, 'children' | 'style'> & {
|
|
18
|
+
isActive?: boolean,
|
|
19
|
+
children?: ReactNode,
|
|
20
|
+
style?: StyleProp<ViewStyle>,
|
|
21
|
+
chipStyle?: StyleOverwrite<ChatQuickReplyChipState, ChatQuickReplyChipStyle>,
|
|
22
|
+
textStyle?: StyleOverwrite<ChatQuickReplyChipState, ChatQuickReplyChipTextStyle>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type PressableInteraction = {
|
|
26
|
+
pressed: boolean,
|
|
27
|
+
hovered?: boolean,
|
|
28
|
+
focused?: boolean,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const ChatQuickReplyChip = ({
|
|
32
|
+
isActive = false,
|
|
33
|
+
children,
|
|
34
|
+
disabled,
|
|
35
|
+
style,
|
|
36
|
+
chipStyle,
|
|
37
|
+
textStyle,
|
|
38
|
+
...props
|
|
39
|
+
}: ChatQuickReplyChipProps) => {
|
|
40
|
+
const { theme } = useTheme()
|
|
41
|
+
|
|
42
|
+
const resolveState = (interaction: PressableInteraction): ChatQuickReplyChipState => ({
|
|
43
|
+
isActive,
|
|
44
|
+
isDisabled: !!disabled,
|
|
45
|
+
isPressed: interaction.pressed,
|
|
46
|
+
isHovered: !!interaction.hovered,
|
|
47
|
+
isFocused: !!interaction.focused,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Pressable
|
|
52
|
+
{...props}
|
|
53
|
+
disabled={disabled}
|
|
54
|
+
style={(pressableState) => {
|
|
55
|
+
const state = resolveState(pressableState as PressableInteraction)
|
|
56
|
+
return [theme.components.chat.quickReplyChip(state, chipStyle), style]
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
{(pressableState) => {
|
|
60
|
+
const state = resolveState(pressableState as PressableInteraction)
|
|
61
|
+
const resolvedText = theme.components.chat.quickReplyChipText(state, textStyle)
|
|
62
|
+
|
|
63
|
+
if (typeof children === 'string' || typeof children === 'number') {
|
|
64
|
+
return <Text style={resolvedText}>{children}</Text>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return children
|
|
68
|
+
}}
|
|
69
|
+
</Pressable>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useMemo, type ReactNode } from 'react'
|
|
2
|
+
import { Text, View, type StyleProp, type ViewProps, type ViewStyle } from 'react-native'
|
|
3
|
+
import { CheckCheck } from 'lucide-react-native'
|
|
4
|
+
import type { ColoringType } from '@helpwave/hightide-design'
|
|
5
|
+
import { useTheme } from '../../global-contexts/theme'
|
|
6
|
+
import type {
|
|
7
|
+
ChatSystemLineState,
|
|
8
|
+
ChatSystemLineStyle,
|
|
9
|
+
ChatSystemLineTextStyle,
|
|
10
|
+
StyleOverwrite
|
|
11
|
+
} from '../../theme'
|
|
12
|
+
|
|
13
|
+
export type ChatSystemLineProps = Omit<ViewProps, 'children' | 'style'> & {
|
|
14
|
+
icon?: ReactNode,
|
|
15
|
+
color?: ColoringType,
|
|
16
|
+
children?: ReactNode,
|
|
17
|
+
style?: StyleProp<ViewStyle>,
|
|
18
|
+
lineStyle?: StyleOverwrite<ChatSystemLineState, ChatSystemLineStyle>,
|
|
19
|
+
textStyle?: StyleOverwrite<ChatSystemLineState, ChatSystemLineTextStyle>,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const ChatSystemLine = ({
|
|
23
|
+
icon,
|
|
24
|
+
color = 'primary',
|
|
25
|
+
children,
|
|
26
|
+
style,
|
|
27
|
+
lineStyle,
|
|
28
|
+
textStyle,
|
|
29
|
+
...props
|
|
30
|
+
}: ChatSystemLineProps) => {
|
|
31
|
+
const { theme } = useTheme()
|
|
32
|
+
const state = useMemo(() => ({ color }), [color])
|
|
33
|
+
|
|
34
|
+
const resolvedLineStyle = useMemo(
|
|
35
|
+
() => theme.components.chat.systemLine(state, lineStyle),
|
|
36
|
+
[theme, state, lineStyle]
|
|
37
|
+
)
|
|
38
|
+
const resolvedTextStyle = useMemo(
|
|
39
|
+
() => theme.components.chat.systemLineText(state, textStyle),
|
|
40
|
+
[theme, state, textStyle]
|
|
41
|
+
)
|
|
42
|
+
const resolvedIcon = useMemo(
|
|
43
|
+
() => theme.components.chat.systemLineIcon(state),
|
|
44
|
+
[theme, state]
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<View {...props} style={[resolvedLineStyle, style]}>
|
|
49
|
+
{icon ?? <CheckCheck size={14} color={resolvedIcon.color} />}
|
|
50
|
+
{typeof children === 'string' || typeof children === 'number' ? (
|
|
51
|
+
<Text style={resolvedTextStyle}>{children}</Text>
|
|
52
|
+
) : (
|
|
53
|
+
children
|
|
54
|
+
)}
|
|
55
|
+
</View>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useMemo, type ReactNode } from 'react'
|
|
2
|
+
import { Text, View, type StyleProp, type ViewProps, type ViewStyle } from 'react-native'
|
|
3
|
+
import { useTheme } from '../../global-contexts/theme'
|
|
4
|
+
import type {
|
|
5
|
+
ChatThreadHeaderStyle,
|
|
6
|
+
ChatThreadHeaderSubtitleStyle,
|
|
7
|
+
ChatThreadHeaderTitleStyle,
|
|
8
|
+
StyleOverwrite
|
|
9
|
+
} from '../../theme'
|
|
10
|
+
|
|
11
|
+
export type ChatThreadHeaderProps = Omit<ViewProps, 'style'> & {
|
|
12
|
+
avatar?: ReactNode,
|
|
13
|
+
title: ReactNode,
|
|
14
|
+
subtitle?: ReactNode,
|
|
15
|
+
leftActions?: ReactNode,
|
|
16
|
+
rightActions?: ReactNode,
|
|
17
|
+
style?: StyleProp<ViewStyle>,
|
|
18
|
+
headerStyle?: StyleOverwrite<Record<string, never>, ChatThreadHeaderStyle>,
|
|
19
|
+
titleStyle?: StyleOverwrite<Record<string, never>, ChatThreadHeaderTitleStyle>,
|
|
20
|
+
subtitleStyle?: StyleOverwrite<Record<string, never>, ChatThreadHeaderSubtitleStyle>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const ChatThreadHeader = ({
|
|
24
|
+
avatar,
|
|
25
|
+
title,
|
|
26
|
+
subtitle,
|
|
27
|
+
leftActions,
|
|
28
|
+
rightActions,
|
|
29
|
+
style,
|
|
30
|
+
headerStyle,
|
|
31
|
+
titleStyle,
|
|
32
|
+
subtitleStyle,
|
|
33
|
+
...props
|
|
34
|
+
}: ChatThreadHeaderProps) => {
|
|
35
|
+
const { theme } = useTheme()
|
|
36
|
+
const state = useMemo(() => ({}), [])
|
|
37
|
+
|
|
38
|
+
const resolvedHeaderStyle = useMemo(
|
|
39
|
+
() => theme.components.chat.threadHeader(state, headerStyle),
|
|
40
|
+
[theme, state, headerStyle]
|
|
41
|
+
)
|
|
42
|
+
const resolvedTitleStyle = useMemo(
|
|
43
|
+
() => theme.components.chat.threadHeaderTitle(state, titleStyle),
|
|
44
|
+
[theme, state, titleStyle]
|
|
45
|
+
)
|
|
46
|
+
const resolvedSubtitleStyle = useMemo(
|
|
47
|
+
() => theme.components.chat.threadHeaderSubtitle(state, subtitleStyle),
|
|
48
|
+
[theme, state, subtitleStyle]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<View {...props} style={[resolvedHeaderStyle, style]}>
|
|
53
|
+
{leftActions != null && (
|
|
54
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', flexShrink: 0 }}>
|
|
55
|
+
{leftActions}
|
|
56
|
+
</View>
|
|
57
|
+
)}
|
|
58
|
+
{avatar}
|
|
59
|
+
<View style={{ flex: 1, minWidth: 0, gap: 2 }}>
|
|
60
|
+
{typeof title === 'string' || typeof title === 'number' ? (
|
|
61
|
+
<Text style={resolvedTitleStyle} numberOfLines={1}>{title}</Text>
|
|
62
|
+
) : (
|
|
63
|
+
title
|
|
64
|
+
)}
|
|
65
|
+
{subtitle != null && (
|
|
66
|
+
typeof subtitle === 'string' || typeof subtitle === 'number' ? (
|
|
67
|
+
<Text style={resolvedSubtitleStyle} numberOfLines={1}>{subtitle}</Text>
|
|
68
|
+
) : (
|
|
69
|
+
subtitle
|
|
70
|
+
)
|
|
71
|
+
)}
|
|
72
|
+
</View>
|
|
73
|
+
{rightActions != null && (
|
|
74
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', flexShrink: 0 }}>
|
|
75
|
+
{rightActions}
|
|
76
|
+
</View>
|
|
77
|
+
)}
|
|
78
|
+
</View>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './ChatAttachmentCard'
|
|
2
|
+
export * from './ChatConversationList'
|
|
3
|
+
export * from './ChatConversationRow'
|
|
4
|
+
export * from './ChatDateDivider'
|
|
5
|
+
export * from './ChatMessageBubble'
|
|
6
|
+
export * from './ChatMessageCard'
|
|
7
|
+
export * from './ChatMessageComposer'
|
|
8
|
+
export * from './ChatMessageList'
|
|
9
|
+
export * from './ChatQuickReplyChip'
|
|
10
|
+
export * from './ChatSystemLine'
|
|
11
|
+
export * from './ChatThreadHeader'
|
package/src/components/index.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
export * from './
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './Input'
|
|
6
|
-
export * from './MultiSelect'
|
|
7
|
-
export * from './Select'
|
|
1
|
+
export * from './chat'
|
|
2
|
+
export * from './menu'
|
|
3
|
+
export * from './user-interaction'
|
|
4
|
+
export * from './visualization-and-display'
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useMemo, type ReactNode } from 'react'
|
|
2
|
+
import { Text, View, type StyleProp, type ViewProps, type ViewStyle } from 'react-native'
|
|
3
|
+
import { useTheme } from '../../global-contexts/theme'
|
|
4
|
+
import type {
|
|
5
|
+
MenuCardStyle,
|
|
6
|
+
MenuSectionStyle,
|
|
7
|
+
MenuSectionTitleStyle,
|
|
8
|
+
StyleOverwrite
|
|
9
|
+
} from '../../theme'
|
|
10
|
+
|
|
11
|
+
export type MenuProps = Omit<ViewProps, 'children' | 'style'> & {
|
|
12
|
+
title: string,
|
|
13
|
+
children?: ReactNode,
|
|
14
|
+
style?: StyleProp<ViewStyle>,
|
|
15
|
+
sectionStyle?: StyleOverwrite<Record<string, never>, MenuSectionStyle>,
|
|
16
|
+
titleStyle?: StyleOverwrite<Record<string, never>, MenuSectionTitleStyle>,
|
|
17
|
+
cardStyle?: StyleOverwrite<Record<string, never>, MenuCardStyle>,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const Menu = ({
|
|
21
|
+
title,
|
|
22
|
+
children,
|
|
23
|
+
style,
|
|
24
|
+
sectionStyle,
|
|
25
|
+
titleStyle,
|
|
26
|
+
cardStyle,
|
|
27
|
+
...props
|
|
28
|
+
}: MenuProps) => {
|
|
29
|
+
const { theme } = useTheme()
|
|
30
|
+
const state = useMemo(() => ({}), [])
|
|
31
|
+
|
|
32
|
+
const resolvedSectionStyle = useMemo(
|
|
33
|
+
() => theme.components.menu.section(state, sectionStyle),
|
|
34
|
+
[theme, state, sectionStyle]
|
|
35
|
+
)
|
|
36
|
+
const resolvedTitleStyle = useMemo(
|
|
37
|
+
() => theme.components.menu.sectionTitle(state, titleStyle),
|
|
38
|
+
[theme, state, titleStyle]
|
|
39
|
+
)
|
|
40
|
+
const resolvedCardStyle = useMemo(
|
|
41
|
+
() => theme.components.menu.card(state, cardStyle),
|
|
42
|
+
[theme, state, cardStyle]
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<View {...props} style={[resolvedSectionStyle, style]}>
|
|
47
|
+
<Text style={resolvedTitleStyle}>{title}</Text>
|
|
48
|
+
<View style={resolvedCardStyle}>{children}</View>
|
|
49
|
+
</View>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Fragment, useMemo, type ReactNode } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
Text,
|
|
5
|
+
View,
|
|
6
|
+
type PressableProps,
|
|
7
|
+
type StyleProp,
|
|
8
|
+
type ViewStyle
|
|
9
|
+
} from 'react-native'
|
|
10
|
+
import { useTheme } from '../../global-contexts/theme'
|
|
11
|
+
import type {
|
|
12
|
+
MenuActionItemLabelStyle,
|
|
13
|
+
MenuActionItemState,
|
|
14
|
+
MenuActionItemStyle,
|
|
15
|
+
StyleOverwrite
|
|
16
|
+
} from '../../theme'
|
|
17
|
+
|
|
18
|
+
export type MenuActionItemProps = Omit<PressableProps, 'children' | 'style'> & {
|
|
19
|
+
label: string,
|
|
20
|
+
leading?: ReactNode,
|
|
21
|
+
trailing?: ReactNode,
|
|
22
|
+
danger?: boolean,
|
|
23
|
+
style?: StyleProp<ViewStyle>,
|
|
24
|
+
itemStyle?: StyleOverwrite<MenuActionItemState, MenuActionItemStyle>,
|
|
25
|
+
labelStyle?: StyleOverwrite<MenuActionItemState, MenuActionItemLabelStyle>,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type PressableInteraction = {
|
|
29
|
+
pressed: boolean,
|
|
30
|
+
hovered?: boolean,
|
|
31
|
+
focused?: boolean,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const MenuActionItem = ({
|
|
35
|
+
label,
|
|
36
|
+
leading,
|
|
37
|
+
trailing,
|
|
38
|
+
danger = false,
|
|
39
|
+
disabled,
|
|
40
|
+
style,
|
|
41
|
+
itemStyle,
|
|
42
|
+
labelStyle,
|
|
43
|
+
...props
|
|
44
|
+
}: MenuActionItemProps) => {
|
|
45
|
+
const { theme } = useTheme()
|
|
46
|
+
|
|
47
|
+
const resolveState = (interaction: PressableInteraction): MenuActionItemState => ({
|
|
48
|
+
isDanger: danger,
|
|
49
|
+
isDisabled: !!disabled,
|
|
50
|
+
isPressed: interaction.pressed,
|
|
51
|
+
isHovered: !!interaction.hovered,
|
|
52
|
+
isFocused: !!interaction.focused,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const resolvedContentStyle = useMemo(
|
|
56
|
+
() => theme.components.menu.actionItemContent({}),
|
|
57
|
+
[theme]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Pressable
|
|
62
|
+
{...props}
|
|
63
|
+
disabled={disabled}
|
|
64
|
+
style={(pressableState) => {
|
|
65
|
+
const state = resolveState(pressableState as PressableInteraction)
|
|
66
|
+
return [theme.components.menu.actionItem(state, itemStyle), style]
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
{(pressableState) => {
|
|
70
|
+
const state = resolveState(pressableState as PressableInteraction)
|
|
71
|
+
const resolvedLabelStyle = theme.components.menu.actionItemLabel(state, labelStyle)
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Fragment>
|
|
75
|
+
{leading}
|
|
76
|
+
<View style={resolvedContentStyle}>
|
|
77
|
+
<Text style={resolvedLabelStyle}>{label}</Text>
|
|
78
|
+
</View>
|
|
79
|
+
{trailing}
|
|
80
|
+
</Fragment>
|
|
81
|
+
)
|
|
82
|
+
}}
|
|
83
|
+
</Pressable>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useMemo, type ReactNode } from 'react'
|
|
2
|
+
import { Text, View, type StyleProp, type ViewProps, type ViewStyle } from 'react-native'
|
|
3
|
+
import { useTheme } from '../../global-contexts/theme'
|
|
4
|
+
import type {
|
|
5
|
+
MenuItemLabelStyle,
|
|
6
|
+
MenuItemStyle,
|
|
7
|
+
MenuItemValueStyle,
|
|
8
|
+
StyleOverwrite
|
|
9
|
+
} from '../../theme'
|
|
10
|
+
|
|
11
|
+
export type MenuItemProps = Omit<ViewProps, 'style'> & {
|
|
12
|
+
label: string,
|
|
13
|
+
value: string,
|
|
14
|
+
leading?: ReactNode,
|
|
15
|
+
trailing?: ReactNode,
|
|
16
|
+
style?: StyleProp<ViewStyle>,
|
|
17
|
+
itemStyle?: StyleOverwrite<Record<string, never>, MenuItemStyle>,
|
|
18
|
+
labelStyle?: StyleOverwrite<Record<string, never>, MenuItemLabelStyle>,
|
|
19
|
+
valueStyle?: StyleOverwrite<Record<string, never>, MenuItemValueStyle>,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const MenuItem = ({
|
|
23
|
+
label,
|
|
24
|
+
value,
|
|
25
|
+
leading,
|
|
26
|
+
trailing,
|
|
27
|
+
style,
|
|
28
|
+
itemStyle,
|
|
29
|
+
labelStyle,
|
|
30
|
+
valueStyle,
|
|
31
|
+
...props
|
|
32
|
+
}: MenuItemProps) => {
|
|
33
|
+
const { theme } = useTheme()
|
|
34
|
+
const state = useMemo(() => ({}), [])
|
|
35
|
+
|
|
36
|
+
const resolvedItemStyle = useMemo(
|
|
37
|
+
() => theme.components.menu.item(state, itemStyle),
|
|
38
|
+
[theme, state, itemStyle]
|
|
39
|
+
)
|
|
40
|
+
const resolvedContentStyle = useMemo(
|
|
41
|
+
() => theme.components.menu.itemContent(state),
|
|
42
|
+
[theme, state]
|
|
43
|
+
)
|
|
44
|
+
const resolvedLabelStyle = useMemo(
|
|
45
|
+
() => theme.components.menu.itemLabel(state, labelStyle),
|
|
46
|
+
[theme, state, labelStyle]
|
|
47
|
+
)
|
|
48
|
+
const resolvedValueStyle = useMemo(
|
|
49
|
+
() => theme.components.menu.itemValue(state, valueStyle),
|
|
50
|
+
[theme, state, valueStyle]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<View {...props} style={[resolvedItemStyle, style]}>
|
|
55
|
+
{leading}
|
|
56
|
+
<View style={resolvedContentStyle}>
|
|
57
|
+
<Text style={resolvedLabelStyle}>{label}</Text>
|
|
58
|
+
<Text style={resolvedValueStyle}>{value}</Text>
|
|
59
|
+
</View>
|
|
60
|
+
{trailing}
|
|
61
|
+
</View>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { ChevronRight } from 'lucide-react-native'
|
|
2
|
+
import { Fragment, useMemo, type ReactNode } from 'react'
|
|
3
|
+
import {
|
|
4
|
+
Pressable,
|
|
5
|
+
Text,
|
|
6
|
+
View,
|
|
7
|
+
type PressableProps,
|
|
8
|
+
type StyleProp,
|
|
9
|
+
type ViewStyle
|
|
10
|
+
} from 'react-native'
|
|
11
|
+
import { useTheme } from '../../global-contexts/theme'
|
|
12
|
+
import type {
|
|
13
|
+
MenuActionItemLabelStyle,
|
|
14
|
+
MenuActionItemState,
|
|
15
|
+
MenuActionItemStyle,
|
|
16
|
+
StyleOverwrite
|
|
17
|
+
} from '../../theme'
|
|
18
|
+
|
|
19
|
+
export type MenuNavigationItemProps = Omit<PressableProps, 'children' | 'style'> & {
|
|
20
|
+
label: string,
|
|
21
|
+
leading?: ReactNode,
|
|
22
|
+
style?: StyleProp<ViewStyle>,
|
|
23
|
+
itemStyle?: StyleOverwrite<MenuActionItemState, MenuActionItemStyle>,
|
|
24
|
+
labelStyle?: StyleOverwrite<MenuActionItemState, MenuActionItemLabelStyle>,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type PressableInteraction = {
|
|
28
|
+
pressed: boolean,
|
|
29
|
+
hovered?: boolean,
|
|
30
|
+
focused?: boolean,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const MenuNavigationItem = ({
|
|
34
|
+
label,
|
|
35
|
+
leading,
|
|
36
|
+
disabled,
|
|
37
|
+
style,
|
|
38
|
+
itemStyle,
|
|
39
|
+
labelStyle,
|
|
40
|
+
...props
|
|
41
|
+
}: MenuNavigationItemProps) => {
|
|
42
|
+
const { theme } = useTheme()
|
|
43
|
+
|
|
44
|
+
const resolveState = (interaction: PressableInteraction): MenuActionItemState => ({
|
|
45
|
+
isDisabled: !!disabled,
|
|
46
|
+
isPressed: interaction.pressed,
|
|
47
|
+
isHovered: !!interaction.hovered,
|
|
48
|
+
isFocused: !!interaction.focused,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const resolvedContentStyle = useMemo(
|
|
52
|
+
() => theme.components.menu.navigationItemContent({}),
|
|
53
|
+
[theme]
|
|
54
|
+
)
|
|
55
|
+
const trailingColor = useMemo(
|
|
56
|
+
() => theme.components.menu.navigationItemTrailing({}).color,
|
|
57
|
+
[theme]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Pressable
|
|
62
|
+
{...props}
|
|
63
|
+
disabled={disabled}
|
|
64
|
+
style={(pressableState) => {
|
|
65
|
+
const state = resolveState(pressableState as PressableInteraction)
|
|
66
|
+
return [theme.components.menu.navigationItem(state, itemStyle), style]
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
{(pressableState) => {
|
|
70
|
+
const state = resolveState(pressableState as PressableInteraction)
|
|
71
|
+
const resolvedLabelStyle = theme.components.menu.navigationItemLabel(state, labelStyle)
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Fragment>
|
|
75
|
+
{leading}
|
|
76
|
+
<View style={resolvedContentStyle}>
|
|
77
|
+
<Text style={resolvedLabelStyle}>{label}</Text>
|
|
78
|
+
</View>
|
|
79
|
+
<ChevronRight size={16} color={trailingColor} />
|
|
80
|
+
</Fragment>
|
|
81
|
+
)
|
|
82
|
+
}}
|
|
83
|
+
</Pressable>
|
|
84
|
+
)
|
|
85
|
+
}
|