@helpwave/hightide-native 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/package.json +2 -2
  2. package/src/components/chat/ChatConversationRow.tsx +127 -27
  3. package/src/components/chat/ChatThreadHeader.tsx +88 -48
  4. package/src/components/user-interaction/MultiSelect.tsx +66 -38
  5. package/src/components/user-interaction/Select.tsx +4 -0
  6. package/src/global-contexts/HightideProvider.tsx +9 -17
  7. package/src/theme/adapters/index.ts +1 -4
  8. package/src/theme/adapters/style-adapter-utils.ts +433 -0
  9. package/src/theme/resolvers/avatar.ts +9 -10
  10. package/src/theme/resolvers/button.ts +6 -7
  11. package/src/theme/resolvers/card.ts +3 -2
  12. package/src/theme/resolvers/chat/attachment-message-bubble.ts +22 -38
  13. package/src/theme/resolvers/chat/conversation-list.ts +5 -4
  14. package/src/theme/resolvers/chat/conversation-row.ts +114 -14
  15. package/src/theme/resolvers/chat/date-divider.ts +4 -4
  16. package/src/theme/resolvers/chat/message-bubble.ts +9 -10
  17. package/src/theme/resolvers/chat/message-composer.ts +5 -6
  18. package/src/theme/resolvers/chat/message-list.ts +3 -2
  19. package/src/theme/resolvers/chat/quick-reply-chip.ts +9 -28
  20. package/src/theme/resolvers/chat/system-line.ts +5 -6
  21. package/src/theme/resolvers/chat/thread-header.ts +60 -12
  22. package/src/theme/resolvers/checkbox.ts +6 -6
  23. package/src/theme/resolvers/chip.ts +5 -6
  24. package/src/theme/resolvers/divider.ts +3 -3
  25. package/src/theme/resolvers/iconButton.ts +5 -5
  26. package/src/theme/resolvers/input.ts +12 -7
  27. package/src/theme/resolvers/listItem.ts +16 -17
  28. package/src/theme/resolvers/multiSelect.ts +23 -16
  29. package/src/theme/resolvers/searchBar.ts +14 -7
  30. package/src/theme/resolvers/select.ts +22 -15
  31. package/src/theme/resolvers/switch.ts +5 -4
  32. package/src/theme/resolvers/themedPressable.ts +6 -7
  33. package/src/theme/themes/createHightideTheme.ts +13 -4
  34. package/src/theme/types/components/chat.ts +54 -3
  35. package/src/theme/types/components/multiSelect.ts +1 -0
  36. package/src/theme/types/components/select.ts +1 -0
  37. package/src/theme/types/layout.ts +13 -7
  38. package/src/theme/types/semantics.ts +2 -1
  39. package/src/theme/types/theme.ts +40 -12
  40. package/src/theme/types/typography.ts +9 -4
  41. package/src/theme/adapters/container-adapter.ts +0 -323
  42. package/src/theme/adapters/defined.ts +0 -11
  43. package/src/theme/adapters/icon-style-adapter.ts +0 -10
  44. package/src/theme/adapters/text-style-adapter.ts +0 -16
@@ -0,0 +1,433 @@
1
+ import type {
2
+ TextStyle,
3
+ ViewStyle
4
+ } from 'react-native'
5
+
6
+ import type {
7
+ AxisAligmentToken,
8
+ BorderRadiusToken,
9
+ BorderToken,
10
+ ContainerTokens,
11
+ CrossAxisAligmentToken,
12
+ CrossAxisLineAligmentToken,
13
+ IconTokens,
14
+ LayoutDirectionToken,
15
+ MainAxisAligmentToken,
16
+ MarginToken,
17
+ PaddingToken,
18
+ TextStyleTokens
19
+ } from '@helpwave/hightide-design/component-token-resolvers'
20
+ import {
21
+ defaultWritingMode,
22
+ resolveDirectionalTokens
23
+ } from '@helpwave/hightide-design/component-token-resolvers'
24
+ import type { ShadowToken } from '@helpwave/hightide-design/theme-tokens'
25
+ import type { IconStyle } from '../../icons'
26
+
27
+ type OptionalViewStyle<K extends keyof ViewStyle> = {
28
+ [P in K]?: ViewStyle[P]
29
+ }
30
+
31
+ const defined = <T extends object>(style: T): T => {
32
+ const result = {} as T
33
+
34
+ for (const key of Object.keys(style) as (keyof T)[]) {
35
+ if (style[key] !== undefined) {
36
+ result[key] = style[key]
37
+ }
38
+ }
39
+
40
+ return result
41
+ }
42
+
43
+ const toFlexStartEnd = (
44
+ alignment: AxisAligmentToken
45
+ ): 'flex-start' | 'flex-end' | 'center' => {
46
+ if (alignment === 'start') {
47
+ return 'flex-start'
48
+ }
49
+
50
+ if (alignment === 'end') {
51
+ return 'flex-end'
52
+ }
53
+
54
+ return 'center'
55
+ }
56
+
57
+ const shadowStyleAdapter = (
58
+ shadow?: ShadowToken
59
+ ): ViewStyle['boxShadow'] | undefined => {
60
+ if (shadow === undefined) {
61
+ return undefined
62
+ }
63
+
64
+ return [{
65
+ color: shadow.color,
66
+ offsetX: shadow.x,
67
+ offsetY: shadow.y,
68
+ blurRadius: shadow.blur,
69
+ spreadDistance: shadow.spread,
70
+ }]
71
+ }
72
+
73
+ const flexDirectionStyleAdapter = (
74
+ direction?: LayoutDirectionToken
75
+ ): ViewStyle['flexDirection'] | undefined => {
76
+ if (direction === undefined) {
77
+ return undefined
78
+ }
79
+
80
+ return direction === 'horizontal' ? 'row' : 'column'
81
+ }
82
+
83
+ const justifyContentStyleAdapter = (
84
+ alignment?: MainAxisAligmentToken
85
+ ): ViewStyle['justifyContent'] | undefined => {
86
+ if (alignment === undefined) {
87
+ return undefined
88
+ }
89
+
90
+ if (alignment === 'space-between' || alignment === 'space-evenly' || alignment === 'space-around') {
91
+ return alignment
92
+ }
93
+
94
+ return toFlexStartEnd(alignment)
95
+ }
96
+
97
+ const alignItemsStyleAdapter = (
98
+ alignment?: CrossAxisAligmentToken
99
+ ): ViewStyle['alignItems'] | undefined => {
100
+ if (alignment === undefined) {
101
+ return undefined
102
+ }
103
+
104
+ if (alignment === 'stretch') {
105
+ return alignment
106
+ }
107
+
108
+ return toFlexStartEnd(alignment)
109
+ }
110
+
111
+ const alignSelfStyleAdapter = (
112
+ alignment?: CrossAxisAligmentToken
113
+ ): ViewStyle['alignSelf'] | undefined => {
114
+ if (alignment === undefined) {
115
+ return undefined
116
+ }
117
+
118
+ if (alignment === 'stretch') {
119
+ return alignment
120
+ }
121
+
122
+ return toFlexStartEnd(alignment)
123
+ }
124
+
125
+ const alignContentStyleAdapter = (
126
+ alignment?: CrossAxisLineAligmentToken
127
+ ): ViewStyle['alignContent'] | undefined => {
128
+ if (alignment === undefined) {
129
+ return undefined
130
+ }
131
+
132
+ if (
133
+ alignment === 'stretch'
134
+ || alignment === 'space-between'
135
+ || alignment === 'space-evenly'
136
+ || alignment === 'space-around'
137
+ ) {
138
+ return alignment
139
+ }
140
+
141
+ return toFlexStartEnd(alignment)
142
+ }
143
+
144
+ const borderRadiusStyleAdapter = (
145
+ borderRadius?: BorderRadiusToken
146
+ ): OptionalViewStyle<
147
+ | 'borderRadius'
148
+ | 'borderTopLeftRadius'
149
+ | 'borderTopRightRadius'
150
+ | 'borderBottomLeftRadius'
151
+ | 'borderBottomRightRadius'
152
+ > | undefined => {
153
+ if (borderRadius === undefined) {
154
+ return undefined
155
+ }
156
+
157
+ if (borderRadius.type === 'all') {
158
+ return {
159
+ borderRadius: borderRadius.value,
160
+ borderTopLeftRadius: undefined,
161
+ borderTopRightRadius: undefined,
162
+ borderBottomLeftRadius: undefined,
163
+ borderBottomRightRadius: undefined,
164
+ }
165
+ }
166
+
167
+ return {
168
+ borderRadius: undefined,
169
+ borderTopLeftRadius: borderRadius.topLeft,
170
+ borderTopRightRadius: borderRadius.topRight,
171
+ borderBottomLeftRadius: borderRadius.bottomLeft,
172
+ borderBottomRightRadius: borderRadius.bottomRight,
173
+ }
174
+ }
175
+
176
+ const borderWidthStyleAdapter = (
177
+ width?: BorderToken['width']
178
+ ): OptionalViewStyle<
179
+ | 'borderTopWidth'
180
+ | 'borderRightWidth'
181
+ | 'borderBottomWidth'
182
+ | 'borderLeftWidth'
183
+ > | undefined => {
184
+ if (width === undefined) {
185
+ return undefined
186
+ }
187
+
188
+ const sides = resolveDirectionalTokens([width], defaultWritingMode)
189
+
190
+ return {
191
+ borderTopWidth: sides.top,
192
+ borderRightWidth: sides.right,
193
+ borderBottomWidth: sides.bottom,
194
+ borderLeftWidth: sides.left,
195
+ }
196
+ }
197
+
198
+ const borderColorStyleAdapter = (
199
+ color?: BorderToken['color']
200
+ ): OptionalViewStyle<
201
+ | 'borderTopColor'
202
+ | 'borderRightColor'
203
+ | 'borderBottomColor'
204
+ | 'borderLeftColor'
205
+ > | undefined => {
206
+ if (color === undefined) {
207
+ return undefined
208
+ }
209
+
210
+ const sides = resolveDirectionalTokens([color], defaultWritingMode)
211
+
212
+ return {
213
+ borderTopColor: sides.top,
214
+ borderRightColor: sides.right,
215
+ borderBottomColor: sides.bottom,
216
+ borderLeftColor: sides.left,
217
+ }
218
+ }
219
+
220
+ const borderStyleAdapter = (
221
+ border?: BorderToken
222
+ ): OptionalViewStyle<
223
+ | 'borderStyle'
224
+ | 'borderTopWidth'
225
+ | 'borderRightWidth'
226
+ | 'borderBottomWidth'
227
+ | 'borderLeftWidth'
228
+ | 'borderTopColor'
229
+ | 'borderRightColor'
230
+ | 'borderBottomColor'
231
+ | 'borderLeftColor'
232
+ > | undefined => {
233
+ if (border === undefined) {
234
+ return undefined
235
+ }
236
+
237
+ return {
238
+ borderStyle: border.style,
239
+ ...borderWidthStyleAdapter(border.width),
240
+ ...borderColorStyleAdapter(border.color),
241
+ }
242
+ }
243
+
244
+ const paddingStyleAdapter = (
245
+ padding?: PaddingToken
246
+ ): OptionalViewStyle<
247
+ | 'paddingTop'
248
+ | 'paddingRight'
249
+ | 'paddingBottom'
250
+ | 'paddingLeft'
251
+ > | undefined => {
252
+ if (padding === undefined) {
253
+ return undefined
254
+ }
255
+
256
+ const sides = resolveDirectionalTokens([padding], defaultWritingMode)
257
+
258
+ return {
259
+ paddingTop: sides.top,
260
+ paddingRight: sides.right,
261
+ paddingBottom: sides.bottom,
262
+ paddingLeft: sides.left,
263
+ }
264
+ }
265
+
266
+ const marginStyleAdapter = (
267
+ margin?: MarginToken
268
+ ): OptionalViewStyle<
269
+ | 'marginTop'
270
+ | 'marginRight'
271
+ | 'marginBottom'
272
+ | 'marginLeft'
273
+ > | undefined => {
274
+ if (margin === undefined) {
275
+ return undefined
276
+ }
277
+
278
+ const sides = resolveDirectionalTokens([margin], defaultWritingMode)
279
+
280
+ return {
281
+ marginTop: sides.top,
282
+ marginRight: sides.right,
283
+ marginBottom: sides.bottom,
284
+ marginLeft: sides.left,
285
+ }
286
+ }
287
+
288
+ const sizeStyleAdapter = (
289
+ size?: ContainerTokens['size']
290
+ ): OptionalViewStyle<
291
+ | 'width'
292
+ | 'height'
293
+ | 'minWidth'
294
+ | 'minHeight'
295
+ | 'maxWidth'
296
+ | 'maxHeight'
297
+ > | undefined => {
298
+ if (size === undefined) {
299
+ return undefined
300
+ }
301
+
302
+ return {
303
+ width: size.width,
304
+ height: size.height,
305
+ minWidth: size.minWidth,
306
+ minHeight: size.minHeight,
307
+ maxWidth: size.maxWidth,
308
+ maxHeight: size.maxHeight,
309
+ }
310
+ }
311
+
312
+ const outlineStyleAdapter = (
313
+ outline?: ContainerTokens['outline']
314
+ ): OptionalViewStyle<
315
+ | 'outlineColor'
316
+ | 'outlineOffset'
317
+ | 'outlineWidth'
318
+ | 'outlineStyle'
319
+ > | undefined => {
320
+ if (outline === undefined) {
321
+ return undefined
322
+ }
323
+
324
+ return {
325
+ outlineColor: outline.color,
326
+ outlineOffset: outline.offset,
327
+ outlineWidth: outline.width,
328
+ outlineStyle: outline.style,
329
+ }
330
+ }
331
+
332
+ const layoutStyleAdapter = (
333
+ layout?: ContainerTokens['layout']
334
+ ): OptionalViewStyle<
335
+ | 'flexWrap'
336
+ | 'flexGrow'
337
+ | 'flexShrink'
338
+ | 'flexBasis'
339
+ | 'flexDirection'
340
+ | 'justifyContent'
341
+ | 'alignItems'
342
+ | 'alignContent'
343
+ | 'alignSelf'
344
+ | 'gap'
345
+ > | undefined => {
346
+ if (layout === undefined) {
347
+ return undefined
348
+ }
349
+
350
+ return {
351
+ flexWrap: layout.flexWrap,
352
+ flexGrow: layout.flexGrow,
353
+ flexShrink: layout.flexShrink,
354
+ flexBasis: layout.flexBasis,
355
+ flexDirection: flexDirectionStyleAdapter(layout.direction),
356
+ justifyContent: justifyContentStyleAdapter(layout.mainAxisAlignment),
357
+ alignItems: alignItemsStyleAdapter(layout.crossAxisAligment),
358
+ alignContent: alignContentStyleAdapter(layout.crossAxisLineAligment),
359
+ alignSelf: alignSelfStyleAdapter(layout.selfCrossAxisAlignment),
360
+ gap: layout.gap,
361
+ }
362
+ }
363
+
364
+ function containerStyleAdapter(tokens: ContainerTokens): ViewStyle {
365
+ if (tokens === undefined) {
366
+ return {}
367
+ }
368
+
369
+ return defined({
370
+ display: 'flex' as ViewStyle['display'],
371
+ overflow: tokens.overflow,
372
+ backgroundColor: tokens.backgroundColor,
373
+ opacity: tokens.opacity,
374
+ boxShadow: shadowStyleAdapter(tokens.decoration?.shadow),
375
+ ...layoutStyleAdapter(tokens.layout),
376
+ ...sizeStyleAdapter(tokens.size),
377
+ ...borderStyleAdapter(tokens.border),
378
+ ...borderRadiusStyleAdapter(tokens.shape?.borderRadius),
379
+ ...paddingStyleAdapter(tokens.padding),
380
+ ...marginStyleAdapter(tokens.margin),
381
+ ...outlineStyleAdapter(tokens.outline),
382
+ })
383
+ }
384
+
385
+ function textStyleAdapter(tokens: TextStyleTokens): TextStyle {
386
+ if (tokens === undefined) {
387
+ return {}
388
+ }
389
+
390
+ return defined({
391
+ color: tokens.color,
392
+ fontSize: tokens.fontSize,
393
+ fontWeight: tokens.fontWeight,
394
+ fontFamily: tokens.fontFamily,
395
+ lineHeight: tokens.lineHeight,
396
+ textAlign: tokens.textAlign,
397
+ })
398
+ }
399
+
400
+ function iconStyleAdapter(tokens: IconTokens): IconStyle {
401
+ if (tokens === undefined) {
402
+ return {}
403
+ }
404
+
405
+ return defined({
406
+ color: tokens.color,
407
+ size: tokens.size,
408
+ strokeWidth: tokens.strokeWidth,
409
+ })
410
+ }
411
+
412
+ export const StyleAdapterUtils = {
413
+ shadow: shadowStyleAdapter,
414
+ flexDirection: flexDirectionStyleAdapter,
415
+ justifyContent: justifyContentStyleAdapter,
416
+ alignItems: alignItemsStyleAdapter,
417
+ alignSelf: alignSelfStyleAdapter,
418
+ alignContent: alignContentStyleAdapter,
419
+ borderRadius: borderRadiusStyleAdapter,
420
+ borderWidth: borderWidthStyleAdapter,
421
+ borderColor: borderColorStyleAdapter,
422
+ border: borderStyleAdapter,
423
+ padding: paddingStyleAdapter,
424
+ margin: marginStyleAdapter,
425
+ size: sizeStyleAdapter,
426
+ outline: outlineStyleAdapter,
427
+ layout: layoutStyleAdapter,
428
+ container: containerStyleAdapter,
429
+ text: textStyleAdapter,
430
+ icon: iconStyleAdapter,
431
+ }
432
+
433
+ export type StyleAdapters = typeof StyleAdapterUtils
@@ -8,9 +8,6 @@ import {
8
8
  type AvatarWithStatusTokens
9
9
  } from '@helpwave/hightide-design/component-token-resolvers'
10
10
 
11
- import { toContainerStyle } from '../adapters/container-adapter'
12
- import { toIconStyle } from '../adapters/icon-style-adapter'
13
- import { toTextStyle } from '../adapters/text-style-adapter'
14
11
  import type {
15
12
  AvatarGroupContainerStyle,
16
13
  AvatarGroupStackStyle,
@@ -34,6 +31,8 @@ import {
34
31
  } from '../types/resolver'
35
32
  import type { ThemeTokens } from '@helpwave/hightide-design/theme-tokens'
36
33
 
34
+ import { StyleAdapterUtils } from '../adapters'
35
+
37
36
  export const toDesignAvatarSize = (size?: AvatarSize | number): AvatarSize => (
38
37
  typeof size === 'number' ? 'md' : (size ?? 'md')
39
38
  )
@@ -114,7 +113,7 @@ export const createAvatarStyleResolvers = (
114
113
  const groupIndex = state.groupIndex ?? 0
115
114
 
116
115
  return {
117
- ...toContainerStyle(container),
116
+ ...StyleAdapterUtils.container(container),
118
117
  position: state.isGrouped ? 'absolute' : 'relative',
119
118
  overflow: 'hidden',
120
119
  ...(state.isGrouped ? {
@@ -141,13 +140,13 @@ export const createAvatarStyleResolvers = (
141
140
  }
142
141
  }),
143
142
  text: createStyleResolver((state: AvatarState): AvatarTextStyle => ({
144
- ...toTextStyle(resolveTokens(state).text),
143
+ ...StyleAdapterUtils.text(resolveTokens(state).text),
145
144
  textAlign: 'center',
146
145
  })),
147
146
  icon: createValueResolver((state: AvatarState): AvatarIconStyle => {
148
147
  const { icon } = resolveTokens(state)
149
148
 
150
- return toIconStyle({
149
+ return StyleAdapterUtils.icon({
151
150
  size: icon.size ?? themeTokens.icongraphy.sizes.md,
152
151
  strokeWidth: icon.strokeWidth ?? themeTokens.icongraphy.strokeWidth,
153
152
  color: icon.color ?? themeTokens.color.primary.onColor,
@@ -233,7 +232,7 @@ export const createAvatarWithStatusThemeResolvers = (
233
232
  : undefined
234
233
 
235
234
  return {
236
- ...toContainerStyle(
235
+ ...StyleAdapterUtils.container(
237
236
  size === undefined
238
237
  ? statusDot
239
238
  : {
@@ -378,7 +377,7 @@ export const toAvatarGroupThemeResolvers: ComponentThemeResolver<
378
377
  }
379
378
  }
380
379
 
381
- return toContainerStyle(container)
380
+ return StyleAdapterUtils.container(container)
382
381
  }),
383
382
  avatarStack: createStyleResolver((state: AvatarGroupState): AvatarGroupStackStyle => {
384
383
  const tokens = resolve(state)
@@ -403,7 +402,7 @@ export const toAvatarGroupThemeResolvers: ComponentThemeResolver<
403
402
  const height = avatarStack.size?.height
404
403
 
405
404
  return {
406
- ...toContainerStyle(avatarStack),
405
+ ...StyleAdapterUtils.container(avatarStack),
407
406
  position: 'relative',
408
407
  minWidth: width,
409
408
  maxWidth: width,
@@ -415,7 +414,7 @@ export const toAvatarGroupThemeResolvers: ComponentThemeResolver<
415
414
  const { text } = resolve(state)
416
415
 
417
416
  return {
418
- ...toTextStyle(
417
+ ...StyleAdapterUtils.text(
419
418
  typeof state.size === 'number'
420
419
  ? {
421
420
  ...text,
@@ -1,6 +1,3 @@
1
- import { toContainerStyle } from '../adapters/container-adapter'
2
- import { toIconStyle } from '../adapters/icon-style-adapter'
3
- import { toTextStyle } from '../adapters/text-style-adapter'
4
1
  import type {
5
2
  ButtonIconStyle,
6
3
  ButtonState,
@@ -15,6 +12,8 @@ import {
15
12
  type ComponentThemeResolver
16
13
  } from '../types/resolver'
17
14
 
15
+ import { StyleAdapterUtils } from '../adapters'
16
+
18
17
  export const toButtonThemeResolvers: ComponentThemeResolver<ButtonThemeResolvers> = ({
19
18
  themeTokens,
20
19
  semanticTokens,
@@ -33,13 +32,13 @@ export const toButtonThemeResolvers: ComponentThemeResolver<ButtonThemeResolvers
33
32
 
34
33
  return {
35
34
  container: createStyleResolver((state: ButtonState): ButtonStyle => ({
36
- ...toContainerStyle(resolve(state).container),
35
+ ...StyleAdapterUtils.container(resolve(state).container),
37
36
  alignSelf: 'flex-start',
38
37
  })),
39
38
  stateLayer: createStyleResolver((state: ButtonState): ButtonStyle => {
40
39
  const tokens = resolve(state)
41
40
  return {
42
- ...toContainerStyle(tokens.stateLayer),
41
+ ...StyleAdapterUtils.container(tokens.stateLayer),
43
42
  position: 'absolute',
44
43
  top: 0,
45
44
  right: 0,
@@ -48,10 +47,10 @@ export const toButtonThemeResolvers: ComponentThemeResolver<ButtonThemeResolvers
48
47
  }
49
48
  }),
50
49
  icon: createValueResolver((state: ButtonState): ButtonIconStyle => (
51
- toIconStyle(resolve(state).icon)
50
+ StyleAdapterUtils.icon(resolve(state).icon)
52
51
  )),
53
52
  text: createStyleResolver((state: ButtonState): ButtonTextStyle => (
54
- toTextStyle(resolve(state).text)
53
+ StyleAdapterUtils.text(resolve(state).text)
55
54
  )),
56
55
  }
57
56
  }
@@ -1,4 +1,3 @@
1
- import { toContainerStyle } from '../adapters/container-adapter'
2
1
  import type {
3
2
  CardStyle,
4
3
  CardThemeResolvers
@@ -8,6 +7,8 @@ import {
8
7
  type ComponentThemeResolver
9
8
  } from '../types/resolver'
10
9
 
10
+ import { StyleAdapterUtils } from '../adapters'
11
+
11
12
  export const toCardThemeResolvers: ComponentThemeResolver<CardThemeResolvers> = ({
12
13
  themeTokens,
13
14
  semanticTokens,
@@ -20,7 +21,7 @@ export const toCardThemeResolvers: ComponentThemeResolver<CardThemeResolvers> =
20
21
 
21
22
  return {
22
23
  container: createSimpleStyleResolver((): CardStyle => ({
23
- ...toContainerStyle(resolveCard()),
24
+ ...StyleAdapterUtils.container(resolveCard()),
24
25
  overflow: 'hidden',
25
26
  })),
26
27
  }