@helpwave/hightide-native 0.0.1

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 (65) hide show
  1. package/LICENSE +208 -0
  2. package/package.json +93 -0
  3. package/src/components/Button/Button.tsx +101 -0
  4. package/src/components/Button/index.ts +1 -0
  5. package/src/components/Checkbox/Checkbox.tsx +103 -0
  6. package/src/components/Checkbox/index.ts +1 -0
  7. package/src/components/Chip/Chip.tsx +93 -0
  8. package/src/components/Chip/index.ts +1 -0
  9. package/src/components/IconButton/IconButton.tsx +89 -0
  10. package/src/components/IconButton/index.ts +1 -0
  11. package/src/components/Input/Input.tsx +114 -0
  12. package/src/components/Input/index.ts +1 -0
  13. package/src/components/MultiSelect/MultiSelect.tsx +153 -0
  14. package/src/components/MultiSelect/index.ts +1 -0
  15. package/src/components/Select/Select.tsx +136 -0
  16. package/src/components/Select/index.ts +1 -0
  17. package/src/components/index.ts +7 -0
  18. package/src/global-contexts/HightideContext.ts +18 -0
  19. package/src/global-contexts/HightideProvider.tsx +57 -0
  20. package/src/global-contexts/hightide-config/HightideConfigUtils.ts +25 -0
  21. package/src/global-contexts/hightide-config/forward-exports.ts +1 -0
  22. package/src/global-contexts/hightide-config/index.ts +2 -0
  23. package/src/global-contexts/index.ts +6 -0
  24. package/src/global-contexts/localization/LocalizationProvider.tsx +31 -0
  25. package/src/global-contexts/localization/forward-exports.ts +8 -0
  26. package/src/global-contexts/localization/index.ts +2 -0
  27. package/src/global-contexts/theme/ThemeContext.ts +23 -0
  28. package/src/global-contexts/theme/ThemeProvider.tsx +101 -0
  29. package/src/global-contexts/theme/forward-exports.ts +7 -0
  30. package/src/global-contexts/theme/index.ts +3 -0
  31. package/src/global-contexts/translation/forward-exports.ts +11 -0
  32. package/src/global-contexts/translation/index.ts +1 -0
  33. package/src/hooks/index.ts +3 -0
  34. package/src/hooks/useMultiSelect.ts +232 -0
  35. package/src/hooks/useNativeKeyValueStore.ts +59 -0
  36. package/src/hooks/useSelect.ts +198 -0
  37. package/src/icons/Icon.tsx +35 -0
  38. package/src/icons/index.ts +1 -0
  39. package/src/storybook/helper.tsx +36 -0
  40. package/src/theme/index.ts +3 -0
  41. package/src/theme/resolvers/button.ts +74 -0
  42. package/src/theme/resolvers/checkbox.ts +86 -0
  43. package/src/theme/resolvers/chip.ts +64 -0
  44. package/src/theme/resolvers/coloring.ts +90 -0
  45. package/src/theme/resolvers/iconButton.ts +62 -0
  46. package/src/theme/resolvers/index.ts +8 -0
  47. package/src/theme/resolvers/input.ts +48 -0
  48. package/src/theme/resolvers/multiSelect.ts +95 -0
  49. package/src/theme/resolvers/select.ts +77 -0
  50. package/src/theme/themes/createTheme.ts +26 -0
  51. package/src/theme/themes/index.ts +2 -0
  52. package/src/theme/themes/nativeThemes.ts +15 -0
  53. package/src/theme/types/button.ts +41 -0
  54. package/src/theme/types/checkbox.ts +36 -0
  55. package/src/theme/types/chip.ts +41 -0
  56. package/src/theme/types/components.ts +17 -0
  57. package/src/theme/types/iconButton.ts +34 -0
  58. package/src/theme/types/index.ts +10 -0
  59. package/src/theme/types/input.ts +25 -0
  60. package/src/theme/types/multiSelect.ts +65 -0
  61. package/src/theme/types/resolver.ts +10 -0
  62. package/src/theme/types/select.ts +77 -0
  63. package/src/theme/types/theme.ts +13 -0
  64. package/src/types/formField.ts +12 -0
  65. package/src/types/index.ts +1 -0
@@ -0,0 +1,36 @@
1
+ import { Plus } from 'lucide-react-native'
2
+
3
+ const iconSelect = {
4
+ control: 'select',
5
+ options: ['none', 'icon'],
6
+ mapping: {
7
+ none: undefined,
8
+ icon: Plus,
9
+ },
10
+ } as const
11
+
12
+ const exampleSelectValues = [
13
+ 'Strawberry',
14
+ 'Apple',
15
+ 'Banana',
16
+ 'Blueberry',
17
+ 'Mango',
18
+ 'Pineapple',
19
+ 'Grapes',
20
+ 'Orange',
21
+ 'Peach',
22
+ 'Watermelon',
23
+ 'Kiwi',
24
+ 'Cherry',
25
+ 'Lemon',
26
+ 'Papaya',
27
+ 'Raspberry',
28
+ 'Blackberry',
29
+ ] as const
30
+
31
+ export type StorybookHelperSelectType = typeof exampleSelectValues[number]
32
+
33
+ export const StorybookHelper = {
34
+ iconSelect,
35
+ selectValues: exampleSelectValues,
36
+ }
@@ -0,0 +1,3 @@
1
+ export * from './resolvers'
2
+ export * from './themes'
3
+ export * from './types'
@@ -0,0 +1,74 @@
1
+ import {
2
+ componentLayouts,
3
+ toPx,
4
+ type ColoringTokensDefinitions,
5
+ type DesignTheme as DesignTokensTheme,
6
+ type ElementSize,
7
+ type SemanticColors
8
+ } from '@helpwave/hightide-design'
9
+ import type { ButtonState, ButtonStyle, ButtonTextStyle, ButtonTheme } from '../types'
10
+ import { isOutlineColoringStyle, resolveColoringStyles } from './coloring'
11
+
12
+ const buttonFontSizes: Record<ElementSize, number> = {
13
+ xs: 12,
14
+ sm: 14,
15
+ md: 14,
16
+ lg: 18,
17
+ }
18
+
19
+ export type CreateButtonThemeOptions = {
20
+ semantic: SemanticColors,
21
+ coloring: ColoringTokensDefinitions,
22
+ }
23
+
24
+ export const createButtonTheme = ({
25
+ semantic,
26
+ coloring,
27
+ }: CreateButtonThemeOptions): ButtonTheme => {
28
+ const resolveState = (state: ButtonState) => {
29
+ const size = state.size ?? 'md'
30
+ const color = state.color ?? 'primary'
31
+ const coloringStyle = state.coloringStyle ?? 'solid'
32
+ const tokens = coloring[color]
33
+ const resolved = resolveColoringStyles(tokens, coloringStyle, semantic, state)
34
+ const padding = componentLayouts.button[size]
35
+ const sizing = componentLayouts.element[size]
36
+ const outlinePadding = isOutlineColoringStyle(coloringStyle)
37
+
38
+ const button: ButtonStyle = {
39
+ flexDirection: 'row',
40
+ alignItems: 'center',
41
+ justifyContent: 'center',
42
+ backgroundColor: resolved.backgroundColor,
43
+ borderColor: resolved.borderColor,
44
+ borderWidth: resolved.borderWidth,
45
+ paddingVertical: toPx(outlinePadding ? padding.paddingYOutline : padding.paddingY),
46
+ paddingHorizontal: toPx(outlinePadding ? padding.paddingXOutline : padding.paddingX),
47
+ gap: toPx(padding.gap),
48
+ minWidth: toPx(padding.minWidth),
49
+ minHeight: toPx(sizing.height),
50
+ borderRadius: toPx(padding.borderRadius),
51
+ opacity: state.isDisabled ? 0.6 : 1,
52
+ }
53
+
54
+ const text: ButtonTextStyle = {
55
+ color: resolved.color,
56
+ fontSize: buttonFontSizes[size],
57
+ fontWeight: '600',
58
+ }
59
+
60
+ return { button, text }
61
+ }
62
+
63
+ return {
64
+ button: (state) => resolveState(state).button,
65
+ text: (state) => resolveState(state).text,
66
+ }
67
+ }
68
+
69
+ export const createButtonThemeFromDesign = (theme: DesignTokensTheme): ButtonTheme => {
70
+ return createButtonTheme({
71
+ semantic: theme.semantic,
72
+ coloring: theme.coloring,
73
+ })
74
+ }
@@ -0,0 +1,86 @@
1
+ import {
2
+ remToPx,
3
+ spacing,
4
+ type ComponentColors,
5
+ type DesignTheme as DesignTokensTheme,
6
+ type ElementSize,
7
+ type SemanticColors
8
+ } from '@helpwave/hightide-design'
9
+ import type {
10
+ CheckboxSize,
11
+ CheckboxState,
12
+ CheckboxStyle,
13
+ CheckboxTheme
14
+ } from '../types'
15
+
16
+ const checkboxSizes: Record<CheckboxSize, number> = {
17
+ sm: remToPx('1.25rem'),
18
+ md: remToPx('1.5rem'),
19
+ lg: remToPx('2rem'),
20
+ }
21
+
22
+ const checkboxIconSizes: Record<CheckboxSize, Exclude<ElementSize, 'xs'>> = {
23
+ sm: 'sm',
24
+ md: 'md',
25
+ lg: 'lg',
26
+ }
27
+
28
+ export type CreateCheckboxThemeOptions = {
29
+ semantic: SemanticColors,
30
+ component: ComponentColors,
31
+ }
32
+
33
+ export const createCheckboxTheme = ({
34
+ semantic,
35
+ component,
36
+ }: CreateCheckboxThemeOptions): CheckboxTheme => {
37
+ const resolveState = (state: CheckboxState) => {
38
+ const size = state.size ?? 'md'
39
+ const dimension = checkboxSizes[size]
40
+ const isActive = !!(state.isChecked || state.isIndeterminate)
41
+ const showIndicator = !!(state.isIndeterminate || state.alwaysShowCheckIcon || state.isChecked)
42
+
43
+ const borderColor = state.isDisabled
44
+ ? semantic.disabled
45
+ : state.isInvalid
46
+ ? semantic.negative
47
+ : (isActive ? semantic.primary : component.border)
48
+
49
+ const backgroundColor = state.isDisabled
50
+ ? semantic.disabled
51
+ : (isActive ? semantic.primary : component.input.background)
52
+
53
+ const checkbox: CheckboxStyle = {
54
+ width: dimension,
55
+ height: dimension,
56
+ alignItems: 'center',
57
+ justifyContent: 'center',
58
+ borderWidth: remToPx(spacing.coloringOutlineWidth),
59
+ borderColor,
60
+ borderRadius: state.isRounded ? dimension / 2 : 6,
61
+ backgroundColor,
62
+ opacity: state.isDisabled ? 0.6 : 1,
63
+ }
64
+
65
+ return {
66
+ checkbox,
67
+ icon: {
68
+ color: isActive ? semantic.onPrimary : semantic.primary,
69
+ size: checkboxIconSizes[size],
70
+ visible: showIndicator,
71
+ },
72
+ }
73
+ }
74
+
75
+ return {
76
+ checkbox: (state) => resolveState(state).checkbox,
77
+ icon: (state) => resolveState(state).icon,
78
+ }
79
+ }
80
+
81
+ export const createCheckboxThemeFromDesign = (theme: DesignTokensTheme): CheckboxTheme => {
82
+ return createCheckboxTheme({
83
+ semantic: theme.semantic,
84
+ component: theme.component,
85
+ })
86
+ }
@@ -0,0 +1,64 @@
1
+ import {
2
+ componentLayouts,
3
+ toPx,
4
+ type ColoringTokensDefinitions,
5
+ type DesignTheme as DesignTokensTheme,
6
+ type SemanticColors
7
+ } from '@helpwave/hightide-design'
8
+ import type { ChipState, ChipStyle, ChipTextStyle, ChipTheme } from '../types'
9
+ import { resolveColoringStyles } from './coloring'
10
+
11
+ export type CreateChipThemeOptions = {
12
+ semantic: SemanticColors,
13
+ coloring: ColoringTokensDefinitions,
14
+ }
15
+
16
+ export const createChipTheme = ({
17
+ semantic,
18
+ coloring,
19
+ }: CreateChipThemeOptions): ChipTheme => {
20
+ const resolveState = (state: ChipState) => {
21
+ const size = state.size ?? 'md'
22
+ const color = state.color ?? 'neutral'
23
+ const coloringStyle = state.coloringStyle ?? 'solid'
24
+ const tokens = coloring[color]
25
+ const resolved = resolveColoringStyles(tokens, coloringStyle, semantic, state)
26
+ const layout = componentLayouts.chip[size]
27
+
28
+ const chip: ChipStyle = {
29
+ flexDirection: 'row',
30
+ alignItems: 'center',
31
+ justifyContent: 'center',
32
+ alignSelf: 'flex-start',
33
+ backgroundColor: resolved.backgroundColor,
34
+ borderColor: resolved.borderColor,
35
+ borderWidth: resolved.borderWidth,
36
+ paddingVertical: toPx(layout.paddingVertical),
37
+ paddingHorizontal: toPx(layout.paddingHorizontal),
38
+ gap: toPx(layout.gap),
39
+ minHeight: toPx(layout.minHeight),
40
+ borderRadius: toPx(layout.borderRadius),
41
+ opacity: state.isDisabled ? 0.6 : 1,
42
+ }
43
+
44
+ const text: ChipTextStyle = {
45
+ color: resolved.color,
46
+ fontSize: toPx(layout.fontSize),
47
+ fontWeight: '600',
48
+ }
49
+
50
+ return { chip, text }
51
+ }
52
+
53
+ return {
54
+ chip: (state) => resolveState(state).chip,
55
+ text: (state) => resolveState(state).text,
56
+ }
57
+ }
58
+
59
+ export const createChipThemeFromDesign = (theme: DesignTokensTheme): ChipTheme => {
60
+ return createChipTheme({
61
+ semantic: theme.semantic,
62
+ coloring: theme.coloring,
63
+ })
64
+ }
@@ -0,0 +1,90 @@
1
+ import {
2
+ componentLayouts,
3
+ hexWithAlpha,
4
+ toPx,
5
+ type ButtonColoringStyle,
6
+ type ChipColoringStyle,
7
+ type ColorValue,
8
+ type ColoringToken,
9
+ type ColoringStyle,
10
+ type SemanticColors
11
+ } from '@helpwave/hightide-design'
12
+ import type { InteractionState } from '../types'
13
+
14
+ export type ResolvedColoringStyles = {
15
+ backgroundColor: ColorValue | 'transparent',
16
+ color: ColorValue,
17
+ borderColor?: ColorValue,
18
+ borderWidth: number,
19
+ }
20
+
21
+ const usesHover = (state: InteractionState): boolean => {
22
+ return !!(state.isHovered || state.isPressed) && !state.isDisabled
23
+ }
24
+
25
+ export const resolveColoringStyles = (
26
+ tokens: ColoringToken,
27
+ coloringStyle: ColoringStyle,
28
+ semantic: SemanticColors,
29
+ state: InteractionState = {}
30
+ ): ResolvedColoringStyles => {
31
+ const outlineWidth = toPx(componentLayouts.shared.coloringOutlineWidth)
32
+ const hovered = usesHover(state)
33
+
34
+ if (state.isDisabled) {
35
+ return {
36
+ backgroundColor: semantic.disabled,
37
+ color: semantic.onDisabled,
38
+ borderWidth: 0,
39
+ }
40
+ }
41
+
42
+ switch (coloringStyle) {
43
+ case 'solid':
44
+ return {
45
+ backgroundColor: hovered ? tokens.hover : tokens.color,
46
+ color: tokens.onColor,
47
+ borderWidth: 0,
48
+ }
49
+ case 'text':
50
+ return {
51
+ backgroundColor: 'transparent',
52
+ color: hovered
53
+ ? (tokens.textHover ?? tokens.hover)
54
+ : (tokens.text ?? tokens.color),
55
+ borderWidth: 0,
56
+ }
57
+ case 'outline':
58
+ return {
59
+ backgroundColor: 'transparent',
60
+ color: hovered
61
+ ? (tokens.outlineHover ?? tokens.hover)
62
+ : (tokens.outline ?? tokens.color),
63
+ borderColor: hovered
64
+ ? (tokens.outlineHover ?? tokens.hover)
65
+ : (tokens.outline ?? tokens.color),
66
+ borderWidth: outlineWidth,
67
+ }
68
+ case 'tonal':
69
+ return {
70
+ backgroundColor: hexWithAlpha(tokens.tonalBackground ?? tokens.color, hovered ? 0.28 : 0.2),
71
+ color: tokens.tonalText ?? tokens.color,
72
+ borderWidth: 0,
73
+ }
74
+ case 'tonal-outline':
75
+ return {
76
+ backgroundColor: hexWithAlpha(tokens.tonalBackground ?? tokens.color, hovered ? 0.28 : 0.2),
77
+ color: tokens.tonalText ?? tokens.color,
78
+ borderColor: hovered
79
+ ? (tokens.outlineHover ?? tokens.hover)
80
+ : (tokens.outline ?? tokens.color),
81
+ borderWidth: outlineWidth,
82
+ }
83
+ }
84
+ }
85
+
86
+ export const isOutlineColoringStyle = (
87
+ coloringStyle: ButtonColoringStyle | ChipColoringStyle
88
+ ): boolean => {
89
+ return coloringStyle === 'outline' || coloringStyle === 'tonal-outline'
90
+ }
@@ -0,0 +1,62 @@
1
+ import {
2
+ componentLayouts,
3
+ toPx,
4
+ type ColoringTokensDefinitions,
5
+ type DesignTheme as DesignTokensTheme,
6
+ type SemanticColors
7
+ } from '@helpwave/hightide-design'
8
+ import type { IconButtonState, IconButtonStyle, IconButtonTheme } from '../types'
9
+ import { isOutlineColoringStyle, resolveColoringStyles } from './coloring'
10
+
11
+ export type CreateIconButtonThemeOptions = {
12
+ semantic: SemanticColors,
13
+ coloring: ColoringTokensDefinitions,
14
+ }
15
+
16
+ export const createIconButtonTheme = ({
17
+ semantic,
18
+ coloring,
19
+ }: CreateIconButtonThemeOptions): IconButtonTheme => {
20
+ const resolveState = (state: IconButtonState) => {
21
+ const size = state.size ?? 'md'
22
+ const color = state.color ?? 'neutral'
23
+ const coloringStyle = state.coloringStyle ?? 'solid'
24
+ const tokens = coloring[color]
25
+ const resolved = resolveColoringStyles(tokens, coloringStyle, semantic, state)
26
+ const sizing = componentLayouts.element[size]
27
+ const outlineWidth = toPx(componentLayouts.shared.coloringOutlineWidth)
28
+ const dimension = toPx(sizing.height)
29
+ const borderWidth = resolved.borderWidth > 0
30
+ ? resolved.borderWidth
31
+ : (isOutlineColoringStyle(coloringStyle) ? outlineWidth : 0)
32
+
33
+ const button: IconButtonStyle = {
34
+ alignItems: 'center',
35
+ justifyContent: 'center',
36
+ backgroundColor: resolved.backgroundColor,
37
+ borderColor: resolved.borderColor,
38
+ borderWidth,
39
+ width: dimension,
40
+ height: dimension,
41
+ borderRadius: toPx(componentLayouts.button[size].borderRadius),
42
+ opacity: state.isDisabled ? 0.6 : 1,
43
+ }
44
+
45
+ return {
46
+ button,
47
+ icon: { color: resolved.color },
48
+ }
49
+ }
50
+
51
+ return {
52
+ button: (state) => resolveState(state).button,
53
+ icon: (state) => resolveState(state).icon,
54
+ }
55
+ }
56
+
57
+ export const createIconButtonThemeFromDesign = (theme: DesignTokensTheme): IconButtonTheme => {
58
+ return createIconButtonTheme({
59
+ semantic: theme.semantic,
60
+ coloring: theme.coloring,
61
+ })
62
+ }
@@ -0,0 +1,8 @@
1
+ export * from './button'
2
+ export * from './checkbox'
3
+ export * from './chip'
4
+ export * from './coloring'
5
+ export * from './iconButton'
6
+ export * from './input'
7
+ export * from './multiSelect'
8
+ export * from './select'
@@ -0,0 +1,48 @@
1
+ import {
2
+ inputElementSizes,
3
+ remToPx,
4
+ type ComponentColors,
5
+ type DesignTheme as DesignTokensTheme,
6
+ type SemanticColors
7
+ } from '@helpwave/hightide-design'
8
+ import type { InputState, InputStyle, InputTheme } from '../types'
9
+
10
+ export type CreateInputThemeOptions = {
11
+ semantic: SemanticColors,
12
+ component: ComponentColors,
13
+ }
14
+
15
+ export const createInputTheme = ({
16
+ semantic,
17
+ component,
18
+ }: CreateInputThemeOptions): InputTheme => {
19
+ const resolveInput = (state: InputState): InputStyle => {
20
+ const sizing = inputElementSizes.md
21
+ const borderColor = state.isInvalid ? semantic.negative : component.border
22
+
23
+ return {
24
+ minHeight: remToPx(sizing.height),
25
+ paddingHorizontal: remToPx(sizing.paddingX),
26
+ paddingVertical: remToPx(sizing.paddingY),
27
+ borderRadius: remToPx(sizing.borderRadius),
28
+ borderWidth: 1,
29
+ borderColor,
30
+ backgroundColor: state.isDisabled ? semantic.disabled : component.input.background,
31
+ color: state.isDisabled ? semantic.onDisabled : component.input.text,
32
+ fontSize: 14,
33
+ opacity: state.isDisabled ? 0.6 : 1,
34
+ }
35
+ }
36
+
37
+ return {
38
+ input: resolveInput,
39
+ placeholderColor: () => semantic.placeholder,
40
+ }
41
+ }
42
+
43
+ export const createInputThemeFromDesign = (theme: DesignTokensTheme): InputTheme => {
44
+ return createInputTheme({
45
+ semantic: theme.semantic,
46
+ component: theme.component,
47
+ })
48
+ }
@@ -0,0 +1,95 @@
1
+ import {
2
+ remToPx,
3
+ type ComponentColors,
4
+ type DesignTheme as DesignTokensTheme,
5
+ type SemanticColors
6
+ } from '@helpwave/hightide-design'
7
+ import type {
8
+ MultiSelectOptionState,
9
+ MultiSelectState,
10
+ MultiSelectTheme
11
+ } from '../types'
12
+
13
+ export type CreateMultiSelectThemeOptions = {
14
+ semantic: SemanticColors,
15
+ component: ComponentColors,
16
+ }
17
+
18
+ export const createMultiSelectTheme = ({
19
+ semantic,
20
+ component,
21
+ }: CreateMultiSelectThemeOptions): MultiSelectTheme => {
22
+ return {
23
+ trigger: (state: MultiSelectState) => ({
24
+ minHeight: 44,
25
+ paddingHorizontal: remToPx('0.75rem'),
26
+ paddingVertical: remToPx('0.5rem'),
27
+ borderRadius: remToPx('0.375rem'),
28
+ borderWidth: 1,
29
+ borderColor: state.isInvalid ? semantic.negative : component.border,
30
+ backgroundColor: state.isDisabled ? semantic.disabled : component.input.background,
31
+ justifyContent: 'center',
32
+ gap: 8,
33
+ opacity: state.isDisabled ? 0.6 : 1,
34
+ }),
35
+ triggerText: () => ({
36
+ color: semantic.placeholder,
37
+ }),
38
+ overlay: () => ({
39
+ flex: 1,
40
+ backgroundColor: '#00000059',
41
+ justifyContent: 'center',
42
+ padding: 24,
43
+ }),
44
+ menu: () => ({
45
+ maxHeight: 360,
46
+ borderRadius: 12,
47
+ backgroundColor: component.menu.background,
48
+ borderWidth: 1,
49
+ borderColor: component.menu.border,
50
+ overflow: 'hidden',
51
+ }),
52
+ search: () => ({
53
+ paddingHorizontal: 16,
54
+ paddingVertical: 12,
55
+ borderBottomWidth: 1,
56
+ borderBottomColor: component.menu.border,
57
+ color: component.menu.text,
58
+ }),
59
+ searchPlaceholderColor: () => semantic.placeholder,
60
+ option: (state: MultiSelectOptionState) => ({
61
+ paddingHorizontal: 16,
62
+ paddingVertical: 12,
63
+ backgroundColor: state.isHighlighted ? component.table.rowHoverBackground : 'transparent',
64
+ opacity: state.isDisabled ? 0.5 : 1,
65
+ flexDirection: 'row',
66
+ alignItems: 'center',
67
+ gap: 10,
68
+ }),
69
+ optionText: (state: MultiSelectOptionState) => ({
70
+ color: state.isSelected ? semantic.primary : component.menu.text,
71
+ fontWeight: state.isSelected ? '600' : '400',
72
+ }),
73
+ checkbox: (state: MultiSelectOptionState) => ({
74
+ width: 18,
75
+ height: 18,
76
+ borderRadius: 4,
77
+ borderWidth: 1,
78
+ borderColor: state.isSelected ? semantic.primary : component.border,
79
+ backgroundColor: state.isSelected ? semantic.primary : 'transparent',
80
+ alignItems: 'center',
81
+ justifyContent: 'center',
82
+ }),
83
+ checkboxIcon: (state: MultiSelectOptionState) => ({
84
+ color: semantic.onPrimary,
85
+ visible: !!state.isSelected,
86
+ }),
87
+ }
88
+ }
89
+
90
+ export const createMultiSelectThemeFromDesign = (theme: DesignTokensTheme): MultiSelectTheme => {
91
+ return createMultiSelectTheme({
92
+ semantic: theme.semantic,
93
+ component: theme.component,
94
+ })
95
+ }
@@ -0,0 +1,77 @@
1
+ import {
2
+ remToPx,
3
+ type ComponentColors,
4
+ type DesignTheme as DesignTokensTheme,
5
+ type SemanticColors
6
+ } from '@helpwave/hightide-design'
7
+ import type {
8
+ SelectOptionState,
9
+ SelectState,
10
+ SelectTheme
11
+ } from '../types'
12
+
13
+ export type CreateSelectThemeOptions = {
14
+ semantic: SemanticColors,
15
+ component: ComponentColors,
16
+ }
17
+
18
+ export const createSelectTheme = ({
19
+ semantic,
20
+ component,
21
+ }: CreateSelectThemeOptions): SelectTheme => {
22
+ return {
23
+ trigger: (state: SelectState) => ({
24
+ minHeight: 44,
25
+ paddingHorizontal: remToPx('0.75rem'),
26
+ paddingVertical: remToPx('0.5rem'),
27
+ borderRadius: remToPx('0.375rem'),
28
+ borderWidth: 1,
29
+ borderColor: state.isInvalid ? semantic.negative : component.border,
30
+ backgroundColor: state.isDisabled ? semantic.disabled : component.input.background,
31
+ justifyContent: 'center',
32
+ opacity: state.isDisabled ? 0.6 : 1,
33
+ }),
34
+ triggerText: (state: SelectState) => ({
35
+ color: state.hasValue ? component.input.text : semantic.placeholder,
36
+ }),
37
+ overlay: () => ({
38
+ flex: 1,
39
+ backgroundColor: '#00000059',
40
+ justifyContent: 'center',
41
+ padding: 24,
42
+ }),
43
+ menu: () => ({
44
+ maxHeight: 360,
45
+ borderRadius: 12,
46
+ backgroundColor: component.menu.background,
47
+ borderWidth: 1,
48
+ borderColor: component.menu.border,
49
+ overflow: 'hidden',
50
+ }),
51
+ search: () => ({
52
+ paddingHorizontal: 16,
53
+ paddingVertical: 12,
54
+ borderBottomWidth: 1,
55
+ borderBottomColor: component.menu.border,
56
+ color: component.menu.text,
57
+ }),
58
+ searchPlaceholderColor: () => semantic.placeholder,
59
+ option: (state: SelectOptionState) => ({
60
+ paddingHorizontal: 16,
61
+ paddingVertical: 12,
62
+ backgroundColor: state.isHighlighted ? component.table.rowHoverBackground : 'transparent',
63
+ opacity: state.isDisabled ? 0.5 : 1,
64
+ }),
65
+ optionText: (state: SelectOptionState) => ({
66
+ color: state.isSelected ? semantic.primary : component.menu.text,
67
+ fontWeight: state.isSelected ? '600' : '400',
68
+ }),
69
+ }
70
+ }
71
+
72
+ export const createSelectThemeFromDesign = (theme: DesignTokensTheme): SelectTheme => {
73
+ return createSelectTheme({
74
+ semantic: theme.semantic,
75
+ component: theme.component,
76
+ })
77
+ }
@@ -0,0 +1,26 @@
1
+ import type { DesignTheme as DesignTokensTheme } from '@helpwave/hightide-design'
2
+ import {
3
+ createButtonThemeFromDesign,
4
+ createCheckboxThemeFromDesign,
5
+ createChipThemeFromDesign,
6
+ createIconButtonThemeFromDesign,
7
+ createInputThemeFromDesign,
8
+ createMultiSelectThemeFromDesign,
9
+ createSelectThemeFromDesign
10
+ } from '../resolvers'
11
+ import type { DesignTheme } from '../types'
12
+
13
+ export const createDesignTheme = (tokens: DesignTokensTheme): DesignTheme => ({
14
+ palettes: tokens.palettes,
15
+ semantic: tokens.semantic,
16
+ coloring: tokens.coloring,
17
+ components: {
18
+ button: createButtonThemeFromDesign(tokens),
19
+ iconButton: createIconButtonThemeFromDesign(tokens),
20
+ chip: createChipThemeFromDesign(tokens),
21
+ checkbox: createCheckboxThemeFromDesign(tokens),
22
+ input: createInputThemeFromDesign(tokens),
23
+ select: createSelectThemeFromDesign(tokens),
24
+ multiSelect: createMultiSelectThemeFromDesign(tokens),
25
+ },
26
+ })
@@ -0,0 +1,2 @@
1
+ export * from './createTheme'
2
+ export * from './nativeThemes'
@@ -0,0 +1,15 @@
1
+ import { themes as designThemes } from '@helpwave/hightide-design'
2
+ import { createDesignTheme } from './createTheme'
3
+
4
+ export const lightTheme = createDesignTheme(designThemes.light)
5
+
6
+ export const darkTheme = createDesignTheme(designThemes.dark)
7
+
8
+ export const themes = {
9
+ light: lightTheme,
10
+ dark: darkTheme,
11
+ } as const
12
+
13
+ export type ThemeMode = keyof typeof themes
14
+
15
+ export const getTheme = (mode: ThemeMode) => themes[mode]