@helpwave/hightide-native 0.0.7 → 0.0.8
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 +2 -2
- package/src/components/user-interaction/Switch.tsx +177 -0
- package/src/components/user-interaction/index.ts +1 -0
- package/src/theme/resolvers/index.ts +1 -0
- package/src/theme/resolvers/switch.ts +65 -0
- package/src/theme/themes/createHightideTheme.ts +2 -0
- package/src/theme/types/components/hightide.ts +2 -0
- package/src/theme/types/components/index.ts +1 -0
- package/src/theme/types/components/switch.ts +15 -0
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
12
|
"license": "AGPL-3.0-only",
|
|
13
|
-
"version": "0.0.
|
|
13
|
+
"version": "0.0.8",
|
|
14
14
|
"type": "module",
|
|
15
15
|
"files": [
|
|
16
16
|
"src"
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"lucide-react-native": "0.561.0",
|
|
46
|
-
"@helpwave/hightide-design": "0.0.
|
|
46
|
+
"@helpwave/hightide-design": "0.0.6",
|
|
47
47
|
"@helpwave/hightide-utils": "0.0.5"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef
|
|
6
|
+
} from 'react'
|
|
7
|
+
import {
|
|
8
|
+
Animated,
|
|
9
|
+
Pressable,
|
|
10
|
+
View,
|
|
11
|
+
type PressableProps,
|
|
12
|
+
type StyleProp,
|
|
13
|
+
type ViewStyle
|
|
14
|
+
} from 'react-native'
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
useControlledState,
|
|
18
|
+
useEventCallbackStabilizer
|
|
19
|
+
} from '@helpwave/hightide-utils/hooks'
|
|
20
|
+
|
|
21
|
+
import { useTheme } from '../../global-contexts/theme/ThemeContext'
|
|
22
|
+
import type { SwitchState } from '../../theme/types/components/switch'
|
|
23
|
+
import type { StyleOverwrite } from '../../theme/types/resolver'
|
|
24
|
+
import type { Color } from '../../theme/types/color'
|
|
25
|
+
import type {
|
|
26
|
+
FormFieldDataHandling,
|
|
27
|
+
FormFieldInteractionStates
|
|
28
|
+
} from '../../types/formField'
|
|
29
|
+
|
|
30
|
+
const TRACK_WIDTH = 48
|
|
31
|
+
const TRACK_HEIGHT = 28
|
|
32
|
+
const TRACK_BORDER_WIDTH = 2
|
|
33
|
+
const TRACK_PADDING = 4
|
|
34
|
+
const THUMB_SIZE_INACTIVE = 12
|
|
35
|
+
const THUMB_SIZE_ACTIVE = 16
|
|
36
|
+
const THUMB_OFFSET_INACTIVE = 2
|
|
37
|
+
const ANIMATION_DURATION_MS = 250
|
|
38
|
+
|
|
39
|
+
const TRACK_INNER_WIDTH = TRACK_WIDTH - (TRACK_BORDER_WIDTH * 2) - (TRACK_PADDING * 2)
|
|
40
|
+
const TRACK_INNER_HEIGHT = TRACK_HEIGHT - (TRACK_BORDER_WIDTH * 2) - (TRACK_PADDING * 2)
|
|
41
|
+
const THUMB_OFFSET_ACTIVE = TRACK_INNER_WIDTH - THUMB_SIZE_ACTIVE
|
|
42
|
+
const THUMB_TOP_INACTIVE = (TRACK_INNER_HEIGHT - THUMB_SIZE_INACTIVE) / 2
|
|
43
|
+
const THUMB_TOP_ACTIVE = (TRACK_INNER_HEIGHT - THUMB_SIZE_ACTIVE) / 2
|
|
44
|
+
|
|
45
|
+
export type SwitchProps = Omit<PressableProps, 'children' | 'style' | 'disabled'>
|
|
46
|
+
& Partial<FormFieldInteractionStates>
|
|
47
|
+
& Partial<FormFieldDataHandling<boolean>>
|
|
48
|
+
& {
|
|
49
|
+
initialValue?: boolean,
|
|
50
|
+
style?: StyleProp<ViewStyle>,
|
|
51
|
+
trackColorStyle?: StyleOverwrite<SwitchState, Color>,
|
|
52
|
+
borderColorStyle?: StyleOverwrite<SwitchState, Color>,
|
|
53
|
+
thumbColorStyle?: StyleOverwrite<SwitchState, Color>,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const Switch = ({
|
|
57
|
+
value: controlledValue,
|
|
58
|
+
initialValue = false,
|
|
59
|
+
invalid = false,
|
|
60
|
+
disabled = false,
|
|
61
|
+
readOnly = false,
|
|
62
|
+
onValueChange,
|
|
63
|
+
onEditComplete,
|
|
64
|
+
style,
|
|
65
|
+
trackColorStyle,
|
|
66
|
+
borderColorStyle,
|
|
67
|
+
thumbColorStyle,
|
|
68
|
+
accessibilityLabel,
|
|
69
|
+
...props
|
|
70
|
+
}: SwitchProps) => {
|
|
71
|
+
const { theme } = useTheme()
|
|
72
|
+
const interactive = !disabled && !readOnly
|
|
73
|
+
|
|
74
|
+
const onEditCompleteStable = useEventCallbackStabilizer(onEditComplete)
|
|
75
|
+
const onValueChangeStable = useEventCallbackStabilizer(onValueChange)
|
|
76
|
+
|
|
77
|
+
const onChangeWrapper = useCallback((nextValue: boolean) => {
|
|
78
|
+
onValueChangeStable(nextValue)
|
|
79
|
+
onEditCompleteStable(nextValue)
|
|
80
|
+
}, [onEditCompleteStable, onValueChangeStable])
|
|
81
|
+
|
|
82
|
+
const [value, setValue] = useControlledState({
|
|
83
|
+
value: controlledValue,
|
|
84
|
+
onValueChange: onChangeWrapper,
|
|
85
|
+
defaultValue: initialValue,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const state = useMemo((): SwitchState => ({
|
|
89
|
+
isActive: value,
|
|
90
|
+
isInvalid: invalid,
|
|
91
|
+
isDisabled: disabled,
|
|
92
|
+
isReadOnly: readOnly,
|
|
93
|
+
}), [disabled, invalid, readOnly, value])
|
|
94
|
+
|
|
95
|
+
const trackColor = useMemo(
|
|
96
|
+
() => theme.components.switch.trackColor(state, trackColorStyle),
|
|
97
|
+
[theme, state, trackColorStyle]
|
|
98
|
+
)
|
|
99
|
+
const borderColor = useMemo(
|
|
100
|
+
() => theme.components.switch.borderColor(state, borderColorStyle),
|
|
101
|
+
[theme, state, borderColorStyle]
|
|
102
|
+
)
|
|
103
|
+
const thumbColor = useMemo(
|
|
104
|
+
() => theme.components.switch.thumbColor(state, thumbColorStyle),
|
|
105
|
+
[theme, state, thumbColorStyle]
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const progress = useRef(new Animated.Value(value ? 1 : 0)).current
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
Animated.timing(progress, {
|
|
112
|
+
toValue: value ? 1 : 0,
|
|
113
|
+
duration: ANIMATION_DURATION_MS,
|
|
114
|
+
useNativeDriver: false,
|
|
115
|
+
}).start()
|
|
116
|
+
}, [progress, value])
|
|
117
|
+
|
|
118
|
+
const thumbSize = progress.interpolate({
|
|
119
|
+
inputRange: [0, 1],
|
|
120
|
+
outputRange: [THUMB_SIZE_INACTIVE, THUMB_SIZE_ACTIVE],
|
|
121
|
+
})
|
|
122
|
+
const thumbTranslateX = progress.interpolate({
|
|
123
|
+
inputRange: [0, 1],
|
|
124
|
+
outputRange: [THUMB_OFFSET_INACTIVE, THUMB_OFFSET_ACTIVE],
|
|
125
|
+
})
|
|
126
|
+
const thumbTop = progress.interpolate({
|
|
127
|
+
inputRange: [0, 1],
|
|
128
|
+
outputRange: [THUMB_TOP_INACTIVE, THUMB_TOP_ACTIVE],
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<Pressable
|
|
133
|
+
{...props}
|
|
134
|
+
disabled={!interactive}
|
|
135
|
+
accessibilityRole="switch"
|
|
136
|
+
accessibilityLabel={accessibilityLabel}
|
|
137
|
+
accessibilityState={{
|
|
138
|
+
checked: value,
|
|
139
|
+
disabled,
|
|
140
|
+
}}
|
|
141
|
+
onPress={(event) => {
|
|
142
|
+
if (interactive) {
|
|
143
|
+
setValue((previous) => !previous)
|
|
144
|
+
}
|
|
145
|
+
props.onPress?.(event)
|
|
146
|
+
}}
|
|
147
|
+
style={style}
|
|
148
|
+
>
|
|
149
|
+
<View
|
|
150
|
+
style={{
|
|
151
|
+
width: TRACK_WIDTH,
|
|
152
|
+
height: TRACK_HEIGHT,
|
|
153
|
+
borderRadius: TRACK_HEIGHT / 2,
|
|
154
|
+
borderWidth: TRACK_BORDER_WIDTH,
|
|
155
|
+
borderColor,
|
|
156
|
+
backgroundColor: trackColor,
|
|
157
|
+
padding: TRACK_PADDING,
|
|
158
|
+
opacity: disabled ? 0.6 : 1,
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
<View style={{ flex: 1 }}>
|
|
162
|
+
<Animated.View
|
|
163
|
+
style={{
|
|
164
|
+
position: 'absolute',
|
|
165
|
+
top: thumbTop,
|
|
166
|
+
width: thumbSize,
|
|
167
|
+
height: thumbSize,
|
|
168
|
+
borderRadius: THUMB_SIZE_ACTIVE / 2,
|
|
169
|
+
backgroundColor: thumbColor,
|
|
170
|
+
transform: [{ translateX: thumbTranslateX }],
|
|
171
|
+
}}
|
|
172
|
+
/>
|
|
173
|
+
</View>
|
|
174
|
+
</View>
|
|
175
|
+
</Pressable>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ComponentColorTokens,
|
|
3
|
+
HightideThemeTokens as DesignTokensTheme
|
|
4
|
+
} from '@helpwave/hightide-design/types'
|
|
5
|
+
|
|
6
|
+
import type { HightideSemanticColors } from '../types/color'
|
|
7
|
+
import type {
|
|
8
|
+
SwitchState,
|
|
9
|
+
SwitchTheme
|
|
10
|
+
} from '../types/components/switch'
|
|
11
|
+
import { createValueResolver } from '../types/resolver'
|
|
12
|
+
|
|
13
|
+
export type CreateSwitchThemeOptions = {
|
|
14
|
+
semantic: HightideSemanticColors,
|
|
15
|
+
component: ComponentColorTokens,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const createSwitchTheme = ({
|
|
19
|
+
semantic,
|
|
20
|
+
component,
|
|
21
|
+
}: CreateSwitchThemeOptions): SwitchTheme => {
|
|
22
|
+
const resolveState = (state: SwitchState) => {
|
|
23
|
+
const trackInactive = state.isDisabled
|
|
24
|
+
? semantic.disabled
|
|
25
|
+
: component.switch.track.inactive
|
|
26
|
+
const trackActive = state.isDisabled
|
|
27
|
+
? semantic.disabled
|
|
28
|
+
: component.switch.track.active
|
|
29
|
+
|
|
30
|
+
const trackColor = state.isActive ? trackActive : trackInactive
|
|
31
|
+
|
|
32
|
+
const borderColor = state.isDisabled
|
|
33
|
+
? semantic.disabled
|
|
34
|
+
: state.isInvalid
|
|
35
|
+
? semantic.negative
|
|
36
|
+
: state.isActive
|
|
37
|
+
? trackActive
|
|
38
|
+
: component.switch.borderColor
|
|
39
|
+
|
|
40
|
+
const thumbColor = state.isDisabled
|
|
41
|
+
? semantic.onDisabled
|
|
42
|
+
: state.isActive
|
|
43
|
+
? component.switch.thumb.active
|
|
44
|
+
: component.switch.thumb.inactive
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
trackColor,
|
|
48
|
+
borderColor,
|
|
49
|
+
thumbColor,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
trackColor: createValueResolver((state) => resolveState(state).trackColor),
|
|
55
|
+
borderColor: createValueResolver((state) => resolveState(state).borderColor),
|
|
56
|
+
thumbColor: createValueResolver((state) => resolveState(state).thumbColor),
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const createSwitchThemeFromDesign = (theme: DesignTokensTheme): SwitchTheme => {
|
|
61
|
+
return createSwitchTheme({
|
|
62
|
+
semantic: theme.semanticColors,
|
|
63
|
+
component: theme.componentColors,
|
|
64
|
+
})
|
|
65
|
+
}
|
|
@@ -8,6 +8,7 @@ import { createButtonThemeFromDesign } from '../resolvers/button'
|
|
|
8
8
|
import { createChatThemeFromDesign } from '../resolvers/chat'
|
|
9
9
|
import { createCheckboxThemeFromDesign } from '../resolvers/checkbox'
|
|
10
10
|
import { createChipThemeFromDesign } from '../resolvers/chip'
|
|
11
|
+
import { createSwitchThemeFromDesign } from '../resolvers/switch'
|
|
11
12
|
import { createIconButtonThemeFromDesign } from '../resolvers/iconButton'
|
|
12
13
|
import { createInputThemeFromDesign } from '../resolvers/input'
|
|
13
14
|
import { createMenuThemeFromDesign } from '../resolvers/menu'
|
|
@@ -47,6 +48,7 @@ export const createHightideTheme = (tokens: HightideThemeTokens): HightideTheme
|
|
|
47
48
|
iconButton: createIconButtonThemeFromDesign(tokens),
|
|
48
49
|
chip: createChipThemeFromDesign(tokens),
|
|
49
50
|
checkbox: createCheckboxThemeFromDesign(tokens),
|
|
51
|
+
switch: createSwitchThemeFromDesign(tokens),
|
|
50
52
|
input: createInputThemeFromDesign(tokens),
|
|
51
53
|
select: createSelectThemeFromDesign(tokens),
|
|
52
54
|
multiSelect: createMultiSelectThemeFromDesign(tokens),
|
|
@@ -9,6 +9,7 @@ import type { InputTheme } from './input'
|
|
|
9
9
|
import type { MenuTheme } from './menu'
|
|
10
10
|
import type { MultiSelectTheme } from './multiSelect'
|
|
11
11
|
import type { SelectTheme } from './select'
|
|
12
|
+
import type { SwitchTheme } from './switch'
|
|
12
13
|
|
|
13
14
|
export type ColoringDefinition = {
|
|
14
15
|
color: Color,
|
|
@@ -35,6 +36,7 @@ export type HightideComponentThemes = {
|
|
|
35
36
|
iconButton: IconButtonTheme,
|
|
36
37
|
chip: ChipTheme,
|
|
37
38
|
checkbox: CheckboxTheme,
|
|
39
|
+
switch: SwitchTheme,
|
|
38
40
|
input: InputTheme,
|
|
39
41
|
select: SelectTheme,
|
|
40
42
|
multiSelect: MultiSelectTheme,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Color } from '../color'
|
|
2
|
+
import type {
|
|
3
|
+
InteractionState,
|
|
4
|
+
StyleResolverFunction
|
|
5
|
+
} from '../resolver'
|
|
6
|
+
|
|
7
|
+
export type SwitchState = InteractionState & {
|
|
8
|
+
isActive?: boolean,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type SwitchTheme = {
|
|
12
|
+
trackColor: StyleResolverFunction<SwitchState, Color>,
|
|
13
|
+
borderColor: StyleResolverFunction<SwitchState, Color>,
|
|
14
|
+
thumbColor: StyleResolverFunction<SwitchState, Color>,
|
|
15
|
+
}
|