@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.
- package/LICENSE +208 -0
- package/package.json +93 -0
- package/src/components/Button/Button.tsx +101 -0
- package/src/components/Button/index.ts +1 -0
- package/src/components/Checkbox/Checkbox.tsx +103 -0
- package/src/components/Checkbox/index.ts +1 -0
- package/src/components/Chip/Chip.tsx +93 -0
- package/src/components/Chip/index.ts +1 -0
- package/src/components/IconButton/IconButton.tsx +89 -0
- package/src/components/IconButton/index.ts +1 -0
- package/src/components/Input/Input.tsx +114 -0
- package/src/components/Input/index.ts +1 -0
- package/src/components/MultiSelect/MultiSelect.tsx +153 -0
- package/src/components/MultiSelect/index.ts +1 -0
- package/src/components/Select/Select.tsx +136 -0
- package/src/components/Select/index.ts +1 -0
- package/src/components/index.ts +7 -0
- package/src/global-contexts/HightideContext.ts +18 -0
- package/src/global-contexts/HightideProvider.tsx +57 -0
- package/src/global-contexts/hightide-config/HightideConfigUtils.ts +25 -0
- package/src/global-contexts/hightide-config/forward-exports.ts +1 -0
- package/src/global-contexts/hightide-config/index.ts +2 -0
- package/src/global-contexts/index.ts +6 -0
- package/src/global-contexts/localization/LocalizationProvider.tsx +31 -0
- package/src/global-contexts/localization/forward-exports.ts +8 -0
- package/src/global-contexts/localization/index.ts +2 -0
- package/src/global-contexts/theme/ThemeContext.ts +23 -0
- package/src/global-contexts/theme/ThemeProvider.tsx +101 -0
- package/src/global-contexts/theme/forward-exports.ts +7 -0
- package/src/global-contexts/theme/index.ts +3 -0
- package/src/global-contexts/translation/forward-exports.ts +11 -0
- package/src/global-contexts/translation/index.ts +1 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useMultiSelect.ts +232 -0
- package/src/hooks/useNativeKeyValueStore.ts +59 -0
- package/src/hooks/useSelect.ts +198 -0
- package/src/icons/Icon.tsx +35 -0
- package/src/icons/index.ts +1 -0
- package/src/storybook/helper.tsx +36 -0
- package/src/theme/index.ts +3 -0
- package/src/theme/resolvers/button.ts +74 -0
- package/src/theme/resolvers/checkbox.ts +86 -0
- package/src/theme/resolvers/chip.ts +64 -0
- package/src/theme/resolvers/coloring.ts +90 -0
- package/src/theme/resolvers/iconButton.ts +62 -0
- package/src/theme/resolvers/index.ts +8 -0
- package/src/theme/resolvers/input.ts +48 -0
- package/src/theme/resolvers/multiSelect.ts +95 -0
- package/src/theme/resolvers/select.ts +77 -0
- package/src/theme/themes/createTheme.ts +26 -0
- package/src/theme/themes/index.ts +2 -0
- package/src/theme/themes/nativeThemes.ts +15 -0
- package/src/theme/types/button.ts +41 -0
- package/src/theme/types/checkbox.ts +36 -0
- package/src/theme/types/chip.ts +41 -0
- package/src/theme/types/components.ts +17 -0
- package/src/theme/types/iconButton.ts +34 -0
- package/src/theme/types/index.ts +10 -0
- package/src/theme/types/input.ts +25 -0
- package/src/theme/types/multiSelect.ts +65 -0
- package/src/theme/types/resolver.ts +10 -0
- package/src/theme/types/select.ts +77 -0
- package/src/theme/types/theme.ts +13 -0
- package/src/types/formField.ts +12 -0
- 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,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,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,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]
|