@hzblj/kara-components 0.1.0 → 0.2.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 (231) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/lib/module/atoms/badge/badge.android.js +8 -2
  3. package/lib/module/atoms/badge/badge.android.js.map +1 -1
  4. package/lib/module/atoms/button/button-tokens.js +39 -0
  5. package/lib/module/atoms/button/button-tokens.js.map +1 -1
  6. package/lib/module/atoms/button/button.android.js +40 -12
  7. package/lib/module/atoms/button/button.android.js.map +1 -1
  8. package/lib/module/atoms/button/button.ios.js +5 -2
  9. package/lib/module/atoms/button/button.ios.js.map +1 -1
  10. package/lib/module/atoms/checkbox/checkbox.android.js +17 -2
  11. package/lib/module/atoms/checkbox/checkbox.android.js.map +1 -1
  12. package/lib/module/atoms/date-picker/date-picker.android.js +72 -0
  13. package/lib/module/atoms/date-picker/date-picker.android.js.map +1 -0
  14. package/lib/module/atoms/date-picker/{date-picker.js → date-picker.ios.js} +4 -7
  15. package/lib/module/atoms/date-picker/date-picker.ios.js.map +1 -0
  16. package/lib/module/atoms/date-picker/index.js +1 -1
  17. package/lib/module/atoms/date-picker/index.js.map +1 -1
  18. package/lib/module/atoms/fab/fab.android.js +5 -3
  19. package/lib/module/atoms/fab/fab.android.js.map +1 -1
  20. package/lib/module/atoms/glass-view/glass-fallback.js +5 -2
  21. package/lib/module/atoms/glass-view/glass-fallback.js.map +1 -1
  22. package/lib/module/atoms/icon/icon.android.js +10 -12
  23. package/lib/module/atoms/icon/icon.android.js.map +1 -1
  24. package/lib/module/atoms/icon/icon.ios.js +15 -3
  25. package/lib/module/atoms/icon/icon.ios.js.map +1 -1
  26. package/lib/module/atoms/icon/index.js +0 -1
  27. package/lib/module/atoms/icon/index.js.map +1 -1
  28. package/lib/module/atoms/index.js +1 -1
  29. package/lib/module/atoms/index.js.map +1 -1
  30. package/lib/module/atoms/list/list.android.js +8 -2
  31. package/lib/module/atoms/list/list.android.js.map +1 -1
  32. package/lib/module/atoms/picker/index.js +1 -1
  33. package/lib/module/atoms/picker/index.js.map +1 -1
  34. package/lib/module/atoms/picker/picker.android.js +87 -0
  35. package/lib/module/atoms/picker/picker.android.js.map +1 -0
  36. package/lib/module/atoms/picker/{picker.js → picker.ios.js} +1 -1
  37. package/lib/module/atoms/picker/picker.ios.js.map +1 -0
  38. package/lib/module/atoms/rn-icon/index.js +5 -0
  39. package/lib/module/atoms/rn-icon/index.js.map +1 -0
  40. package/lib/module/atoms/rn-icon/rn-icon.android.js +29 -0
  41. package/lib/module/atoms/rn-icon/rn-icon.android.js.map +1 -0
  42. package/lib/module/atoms/rn-icon/rn-icon.ios.js +29 -0
  43. package/lib/module/atoms/rn-icon/rn-icon.ios.js.map +1 -0
  44. package/lib/module/atoms/rn-icon/rn-icon.types.js +4 -0
  45. package/lib/module/atoms/rn-icon/rn-icon.types.js.map +1 -0
  46. package/lib/module/atoms/surface/surface.android.js +33 -5
  47. package/lib/module/atoms/surface/surface.android.js.map +1 -1
  48. package/lib/module/atoms/switch/switch.android.js +14 -1
  49. package/lib/module/atoms/switch/switch.android.js.map +1 -1
  50. package/lib/module/glyph/glyph-button.android.js +25 -0
  51. package/lib/module/glyph/glyph-button.android.js.map +1 -0
  52. package/lib/module/glyph/glyph-button.ios.js +33 -0
  53. package/lib/module/glyph/glyph-button.ios.js.map +1 -0
  54. package/lib/module/glyph/glyph-button.types.js +4 -0
  55. package/lib/module/glyph/glyph-button.types.js.map +1 -0
  56. package/lib/module/glyph/glyph.android.js +20 -0
  57. package/lib/module/glyph/glyph.android.js.map +1 -0
  58. package/lib/module/glyph/glyph.ios.js +24 -0
  59. package/lib/module/glyph/glyph.ios.js.map +1 -0
  60. package/lib/module/glyph/glyph.types.js +4 -0
  61. package/lib/module/glyph/glyph.types.js.map +1 -0
  62. package/lib/module/glyph/index.js +8 -0
  63. package/lib/module/glyph/index.js.map +1 -0
  64. package/lib/module/glyph/rn-glyph.android.js +25 -0
  65. package/lib/module/glyph/rn-glyph.android.js.map +1 -0
  66. package/lib/module/glyph/rn-glyph.ios.js +26 -0
  67. package/lib/module/glyph/rn-glyph.ios.js.map +1 -0
  68. package/lib/module/molecules/collapsible/collapsible.android.js +2 -2
  69. package/lib/module/molecules/collapsible/collapsible.android.js.map +1 -1
  70. package/lib/module/molecules/labeled-content/labeled-content.js +2 -1
  71. package/lib/module/molecules/labeled-content/labeled-content.js.map +1 -1
  72. package/lib/module/molecules/list-item/list-item.js +2 -1
  73. package/lib/module/molecules/list-item/list-item.js.map +1 -1
  74. package/lib/module/molecules/navigation-bar/navigation-bar.android.js +8 -4
  75. package/lib/module/molecules/navigation-bar/navigation-bar.android.js.map +1 -1
  76. package/lib/module/molecules/radio-group/radio-group.android.js +5 -0
  77. package/lib/module/molecules/radio-group/radio-group.android.js.map +1 -1
  78. package/lib/module/molecules/radio-group/radio-group.ios.js +2 -2
  79. package/lib/module/molecules/radio-group/radio-group.ios.js.map +1 -1
  80. package/lib/module/molecules/search-bar/search-bar.js +10 -23
  81. package/lib/module/molecules/search-bar/search-bar.js.map +1 -1
  82. package/lib/module/molecules/stepper/stepper.android.js +7 -5
  83. package/lib/module/molecules/stepper/stepper.android.js.map +1 -1
  84. package/lib/module/organisms/alert/alert.android.js +42 -13
  85. package/lib/module/organisms/alert/alert.android.js.map +1 -1
  86. package/lib/module/organisms/context-menu/context-menu.android.js +82 -31
  87. package/lib/module/organisms/context-menu/context-menu.android.js.map +1 -1
  88. package/lib/module/organisms/context-menu/context-menu.ios.js +37 -16
  89. package/lib/module/organisms/context-menu/context-menu.ios.js.map +1 -1
  90. package/lib/module/organisms/fab-menu/fab-menu.android.js +6 -2
  91. package/lib/module/organisms/fab-menu/fab-menu.android.js.map +1 -1
  92. package/lib/module/organisms/fab-menu/fab-menu.ios.js +15 -6
  93. package/lib/module/organisms/fab-menu/fab-menu.ios.js.map +1 -1
  94. package/lib/module/organisms/form/form.android.js +1 -0
  95. package/lib/module/organisms/form/form.android.js.map +1 -1
  96. package/lib/module/organisms/index.js +0 -1
  97. package/lib/module/organisms/index.js.map +1 -1
  98. package/lib/module/organisms/menu/menu.android.js +51 -33
  99. package/lib/module/organisms/menu/menu.android.js.map +1 -1
  100. package/lib/typescript/atoms/button/button-tokens.d.ts +6 -1
  101. package/lib/typescript/atoms/date-picker/date-picker.d.ts +1 -2
  102. package/lib/typescript/atoms/date-picker/date-picker.ios.d.ts +3 -0
  103. package/lib/typescript/atoms/glass-view/glass-fallback.d.ts +4 -1
  104. package/lib/typescript/atoms/glass-view/glass-view.types.d.ts +1 -1
  105. package/lib/typescript/atoms/icon/icon.ios.d.ts +4 -0
  106. package/lib/typescript/atoms/icon/icon.types.d.ts +18 -1
  107. package/lib/typescript/atoms/icon/index.d.ts +0 -1
  108. package/lib/typescript/atoms/index.d.ts +1 -1
  109. package/lib/typescript/atoms/list/list.types.d.ts +9 -0
  110. package/lib/typescript/atoms/picker/picker.d.ts +1 -2
  111. package/lib/typescript/atoms/picker/picker.ios.d.ts +2 -0
  112. package/lib/typescript/atoms/rn-icon/index.d.ts +2 -0
  113. package/lib/typescript/atoms/rn-icon/rn-icon.d.ts +1 -0
  114. package/lib/typescript/atoms/rn-icon/rn-icon.ios.d.ts +3 -0
  115. package/lib/typescript/atoms/rn-icon/rn-icon.types.d.ts +5 -0
  116. package/lib/typescript/glyph/glyph-button.d.ts +1 -0
  117. package/lib/typescript/glyph/glyph-button.ios.d.ts +2 -0
  118. package/lib/typescript/glyph/glyph-button.types.d.ts +10 -0
  119. package/lib/typescript/glyph/glyph.d.ts +1 -0
  120. package/lib/typescript/glyph/glyph.ios.d.ts +3 -0
  121. package/lib/typescript/glyph/glyph.types.d.ts +13 -0
  122. package/lib/typescript/glyph/index.d.ts +5 -0
  123. package/lib/typescript/glyph/rn-glyph.d.ts +1 -0
  124. package/lib/typescript/glyph/rn-glyph.ios.d.ts +3 -0
  125. package/lib/typescript/molecules/scroll-edge-container/scroll-edge-container.types.d.ts +3 -0
  126. package/lib/typescript/organisms/context-menu/context-menu.types.d.ts +28 -2
  127. package/lib/typescript/organisms/fab-menu/fab-menu.types.d.ts +1 -1
  128. package/lib/typescript/organisms/form/form.types.d.ts +5 -0
  129. package/lib/typescript/organisms/index.d.ts +0 -1
  130. package/package.json +4 -6
  131. package/src/atoms/badge/badge.android.tsx +5 -2
  132. package/src/atoms/button/button-tokens.ts +49 -0
  133. package/src/atoms/button/button.android.tsx +46 -12
  134. package/src/atoms/button/button.ios.tsx +5 -2
  135. package/src/atoms/checkbox/checkbox.android.tsx +21 -4
  136. package/src/atoms/date-picker/date-picker.android.tsx +74 -0
  137. package/src/atoms/date-picker/date-picker.ios.tsx +27 -0
  138. package/src/atoms/fab/fab.android.tsx +4 -1
  139. package/src/atoms/glass-view/glass-fallback.tsx +5 -2
  140. package/src/atoms/glass-view/glass-view.types.ts +1 -1
  141. package/src/atoms/icon/icon.android.tsx +11 -7
  142. package/src/atoms/icon/icon.ios.tsx +25 -5
  143. package/src/atoms/icon/icon.types.ts +18 -24
  144. package/src/atoms/icon/index.ts +0 -1
  145. package/src/atoms/index.ts +1 -1
  146. package/src/atoms/list/list.android.tsx +12 -1
  147. package/src/atoms/list/list.types.ts +9 -0
  148. package/src/atoms/picker/picker.android.tsx +88 -0
  149. package/src/atoms/rn-icon/index.ts +2 -0
  150. package/src/atoms/rn-icon/rn-icon.android.tsx +28 -0
  151. package/src/atoms/rn-icon/rn-icon.ios.tsx +28 -0
  152. package/src/atoms/rn-icon/rn-icon.types.ts +8 -0
  153. package/src/atoms/surface/surface.android.tsx +28 -4
  154. package/src/atoms/switch/switch.android.tsx +24 -3
  155. package/src/glyph/glyph-button.android.tsx +25 -0
  156. package/src/glyph/glyph-button.ios.tsx +37 -0
  157. package/src/glyph/glyph-button.types.ts +12 -0
  158. package/src/glyph/glyph.android.tsx +12 -0
  159. package/src/glyph/glyph.ios.tsx +15 -0
  160. package/src/glyph/glyph.types.ts +15 -0
  161. package/src/glyph/index.ts +5 -0
  162. package/src/glyph/rn-glyph.android.tsx +14 -0
  163. package/src/glyph/rn-glyph.ios.tsx +16 -0
  164. package/src/molecules/collapsible/collapsible.android.tsx +2 -2
  165. package/src/molecules/labeled-content/labeled-content.tsx +2 -1
  166. package/src/molecules/list-item/list-item.tsx +2 -1
  167. package/src/molecules/navigation-bar/navigation-bar.android.tsx +11 -4
  168. package/src/molecules/radio-group/radio-group.android.tsx +11 -2
  169. package/src/molecules/radio-group/radio-group.ios.tsx +2 -2
  170. package/src/molecules/scroll-edge-container/scroll-edge-container.types.ts +3 -0
  171. package/src/molecules/search-bar/search-bar.tsx +4 -9
  172. package/src/molecules/stepper/stepper.android.tsx +3 -3
  173. package/src/organisms/alert/alert.android.tsx +44 -7
  174. package/src/organisms/context-menu/context-menu.android.tsx +71 -37
  175. package/src/organisms/context-menu/context-menu.ios.tsx +40 -18
  176. package/src/organisms/context-menu/context-menu.types.ts +29 -3
  177. package/src/organisms/fab-menu/fab-menu.android.tsx +3 -2
  178. package/src/organisms/fab-menu/fab-menu.ios.tsx +13 -12
  179. package/src/organisms/fab-menu/fab-menu.types.ts +1 -1
  180. package/src/organisms/form/form.android.tsx +1 -1
  181. package/src/organisms/form/form.types.ts +5 -0
  182. package/src/organisms/index.ts +0 -1
  183. package/src/organisms/menu/menu.android.tsx +36 -30
  184. package/lib/module/atoms/countdown/countdown-frame.js +0 -115
  185. package/lib/module/atoms/countdown/countdown-frame.js.map +0 -1
  186. package/lib/module/atoms/countdown/countdown-units.js +0 -25
  187. package/lib/module/atoms/countdown/countdown-units.js.map +0 -1
  188. package/lib/module/atoms/countdown/countdown.android.js +0 -53
  189. package/lib/module/atoms/countdown/countdown.android.js.map +0 -1
  190. package/lib/module/atoms/countdown/countdown.ios.js +0 -50
  191. package/lib/module/atoms/countdown/countdown.ios.js.map +0 -1
  192. package/lib/module/atoms/countdown/countdown.types.js +0 -4
  193. package/lib/module/atoms/countdown/countdown.types.js.map +0 -1
  194. package/lib/module/atoms/countdown/index.js +0 -5
  195. package/lib/module/atoms/countdown/index.js.map +0 -1
  196. package/lib/module/atoms/date-picker/date-picker.js.map +0 -1
  197. package/lib/module/atoms/icon/icon-names.js +0 -53
  198. package/lib/module/atoms/icon/icon-names.js.map +0 -1
  199. package/lib/module/atoms/picker/picker.js.map +0 -1
  200. package/lib/module/organisms/rn-context-menu/index.js +0 -5
  201. package/lib/module/organisms/rn-context-menu/index.js.map +0 -1
  202. package/lib/module/organisms/rn-context-menu/rn-context-menu.android.js +0 -101
  203. package/lib/module/organisms/rn-context-menu/rn-context-menu.android.js.map +0 -1
  204. package/lib/module/organisms/rn-context-menu/rn-context-menu.ios.js +0 -44
  205. package/lib/module/organisms/rn-context-menu/rn-context-menu.ios.js.map +0 -1
  206. package/lib/module/organisms/rn-context-menu/rn-context-menu.types.js +0 -4
  207. package/lib/module/organisms/rn-context-menu/rn-context-menu.types.js.map +0 -1
  208. package/lib/typescript/atoms/countdown/countdown-frame.d.ts +0 -17
  209. package/lib/typescript/atoms/countdown/countdown-units.d.ts +0 -5
  210. package/lib/typescript/atoms/countdown/countdown.d.ts +0 -1
  211. package/lib/typescript/atoms/countdown/countdown.ios.d.ts +0 -2
  212. package/lib/typescript/atoms/countdown/countdown.types.d.ts +0 -21
  213. package/lib/typescript/atoms/countdown/index.d.ts +0 -2
  214. package/lib/typescript/atoms/icon/icon-names.d.ts +0 -50
  215. package/lib/typescript/organisms/rn-context-menu/index.d.ts +0 -2
  216. package/lib/typescript/organisms/rn-context-menu/rn-context-menu.d.ts +0 -1
  217. package/lib/typescript/organisms/rn-context-menu/rn-context-menu.ios.d.ts +0 -2
  218. package/lib/typescript/organisms/rn-context-menu/rn-context-menu.types.d.ts +0 -33
  219. package/src/atoms/countdown/countdown-frame.tsx +0 -123
  220. package/src/atoms/countdown/countdown-units.ts +0 -37
  221. package/src/atoms/countdown/countdown.android.tsx +0 -49
  222. package/src/atoms/countdown/countdown.ios.tsx +0 -46
  223. package/src/atoms/countdown/countdown.types.ts +0 -24
  224. package/src/atoms/countdown/index.ts +0 -2
  225. package/src/atoms/date-picker/date-picker.tsx +0 -26
  226. package/src/atoms/icon/icon-names.ts +0 -53
  227. package/src/organisms/rn-context-menu/index.ts +0 -2
  228. package/src/organisms/rn-context-menu/rn-context-menu.android.tsx +0 -90
  229. package/src/organisms/rn-context-menu/rn-context-menu.ios.tsx +0 -47
  230. package/src/organisms/rn-context-menu/rn-context-menu.types.ts +0 -36
  231. /package/src/atoms/picker/{picker.tsx → picker.ios.tsx} +0 -0
@@ -1,123 +0,0 @@
1
- import type {CountdownUnits} from '@hzblj/kara-native'
2
- import {MAX_FONT_SCALE, type TextVariant, useAppTheme} from '@hzblj/kara-theme'
3
- import {type ReactNode, useEffect, useMemo, useState} from 'react'
4
- import {StyleSheet, Text, useWindowDimensions, View} from 'react-native'
5
-
6
- const SECOND_MS = 1000
7
- const HOUR = 3600
8
- const DAY = 86_400
9
- const DIVIDER = ' / '
10
-
11
- /** Wakes a beat after the shape changes, so the frame never reads the boundary second twice. */
12
- const SHAPE_CHANGE_SLACK_MS = 50
13
-
14
- type CountdownShape = {
15
- days: number
16
- hasHours: boolean
17
- }
18
-
19
- const remainingSeconds = (endsAt: number) => Math.max(0, Math.round((endsAt - Date.now()) / SECOND_MS))
20
-
21
- const countdownShape = (endsAt: number): CountdownShape => {
22
- const remaining = remainingSeconds(endsAt)
23
-
24
- return {days: Math.floor(remaining / DAY), hasHours: remaining >= HOUR}
25
- }
26
-
27
- /** When the next segment drops out — the last day, then the last hour — or `null` once only minutes are left. */
28
- const nextShapeChange = (endsAt: number, {days, hasHours}: CountdownShape) => {
29
- if (days > 0) {
30
- return endsAt - (days * DAY - 0.5) * SECOND_MS
31
- }
32
-
33
- if (hasHours) {
34
- return endsAt - (HOUR - 0.5) * SECOND_MS
35
- }
36
-
37
- return null
38
- }
39
-
40
- /** The countdown's shape changes twice at most, so this re-renders twice at most — never once a second. */
41
- const useCountdownShape = (endsAt: number) => {
42
- const [shape, setShape] = useState(() => countdownShape(endsAt))
43
-
44
- useEffect(() => {
45
- let timeout: ReturnType<typeof setTimeout> | undefined
46
-
47
- const update = () => {
48
- const next = countdownShape(endsAt)
49
-
50
- setShape(current => (current.days === next.days && current.hasHours === next.hasHours ? current : next))
51
-
52
- const changesAt = nextShapeChange(endsAt, next)
53
-
54
- if (changesAt !== null) {
55
- timeout = setTimeout(update, Math.max(0, changesAt - Date.now()) + SHAPE_CHANGE_SLACK_MS)
56
- }
57
- }
58
-
59
- update()
60
-
61
- return () => clearTimeout(timeout)
62
- }, [endsAt])
63
-
64
- return shape
65
- }
66
-
67
- /** The widest string the countdown can show in its current shape, with zeros standing in for the digits. */
68
- const countdownTemplate = ({days, hasHours}: CountdownShape, [day, hour, minute, second]: CountdownUnits) => {
69
- let template = ''
70
-
71
- if (days > 0) {
72
- template += `${'0'.repeat(String(days).length)}${day}${DIVIDER}`
73
- }
74
-
75
- if (hasHours) {
76
- template += `00${hour}${DIVIDER}`
77
- }
78
-
79
- return `${template}00${minute}${DIVIDER}00${second}`
80
- }
81
-
82
- const styles = StyleSheet.create({
83
- template: {opacity: 0},
84
- })
85
-
86
- type CountdownFrameProps = {
87
- children: ReactNode
88
- endsAt: number
89
- testID?: string
90
- units: CountdownUnits
91
- variant: TextVariant
92
- }
93
-
94
- /**
95
- * What gives the native countdown its size: an invisible React Native text of exactly the shape it draws, set the way
96
- * `RnText` sets the same variant, so the countdown takes the space any text of that variant would — and the native view
97
- * laid over it has to match it to the point. Accessibility reads the native view, never this.
98
- */
99
- export const CountdownFrame = ({children, endsAt, testID, units, variant}: CountdownFrameProps) => {
100
- const theme = useAppTheme()
101
- const {fontScale} = useWindowDimensions()
102
- const shape = useCountdownShape(endsAt)
103
- const template = useMemo(() => countdownTemplate(shape, units), [shape, units])
104
- const style = theme.typography[variant]
105
-
106
- /** As in `RnText`: React Native grows the glyphs but not the line, so the line grows here by the same capped factor. */
107
- const lineHeight = style.lineHeight * Math.min(fontScale, MAX_FONT_SCALE)
108
-
109
- return (
110
- <View testID={testID}>
111
- <View accessibilityElementsHidden importantForAccessibility="no-hide-descendants" style={styles.template}>
112
- <Text
113
- maxFontSizeMultiplier={MAX_FONT_SCALE}
114
- numberOfLines={1}
115
- style={[style, {fontVariant: ['tabular-nums'], lineHeight}]}
116
- >
117
- {template}
118
- </Text>
119
- </View>
120
- {children}
121
- </View>
122
- )
123
- }
@@ -1,37 +0,0 @@
1
- import type {CountdownUnits} from '@hzblj/kara-native'
2
- import {useMemo} from 'react'
3
-
4
- const FALLBACK_UNITS: CountdownUnits = ['d', 'h', 'm', 's']
5
-
6
- type Unit = 'day' | 'hour' | 'minute' | 'second'
7
-
8
- /** The first letter of the locale's short unit name — `d`, `h`, `m`, `s` in English, `d`, `h`, `m`, `s` in Czech too. */
9
- const unitInitial = (locale: string | undefined, unit: Unit, fallback: string) => {
10
- try {
11
- const parts = new Intl.NumberFormat(locale, {style: 'unit', unit, unitDisplay: 'short'}).formatToParts(1)
12
- const initial = parts
13
- .find(part => part.type === 'unit')
14
- ?.value.trim()
15
- .charAt(0)
16
-
17
- return initial || fallback
18
- } catch {
19
- return fallback
20
- }
21
- }
22
-
23
- /** The caller's units, else the locale's initials, else English ones. */
24
- export const useCountdownUnits = (locale: string | undefined, units: CountdownUnits | undefined): CountdownUnits =>
25
- useMemo(
26
- () =>
27
- units ?? [
28
- unitInitial(locale, 'day', FALLBACK_UNITS[0]),
29
- unitInitial(locale, 'hour', FALLBACK_UNITS[1]),
30
- unitInitial(locale, 'minute', FALLBACK_UNITS[2]),
31
- unitInitial(locale, 'second', FALLBACK_UNITS[3]),
32
- ],
33
- [locale, units]
34
- )
35
-
36
- /** `endsAt` as the epoch milliseconds the native views take. */
37
- export const toEpochMs = (endsAt: Date | number) => (typeof endsAt === 'number' ? endsAt : endsAt.getTime())
@@ -1,49 +0,0 @@
1
- import {Host} from '@expo/ui/jetpack-compose'
2
- import {Countdown as NativeCountdown} from '@hzblj/kara-native/jetpack-compose'
3
- import {MAX_FONT_SCALE, useAppTheme} from '@hzblj/kara-theme'
4
- import {StyleSheet, useWindowDimensions} from 'react-native'
5
- import type {CountdownComponent, CountdownProps} from './countdown.types'
6
- import {CountdownFrame} from './countdown-frame'
7
- import {toEpochMs, useCountdownUnits} from './countdown-units'
8
-
9
- export const Countdown: CountdownComponent = ({
10
- color = 'content.primary',
11
- endsAt,
12
- locale,
13
- testID,
14
- units,
15
- variant = 'body',
16
- }: CountdownProps) => {
17
- const theme = useAppTheme()
18
- const {fontScale} = useWindowDimensions()
19
- const resolvedUnits = useCountdownUnits(locale, units)
20
- const endsAtMs = toEpochMs(endsAt)
21
- const style = theme.typography[variant]
22
-
23
- /**
24
- * Compose reads `sp` and scales it by the font scale itself — uncapped — while the React Native frame under it stops
25
- * at `MAX_FONT_SCALE`. Pre-dividing by the part Compose would add past the cap lands both on the same size.
26
- */
27
- const scale = Math.min(fontScale, MAX_FONT_SCALE) / fontScale
28
-
29
- return (
30
- <CountdownFrame endsAt={endsAtMs} testID={testID} units={resolvedUnits} variant={variant}>
31
- <Host
32
- colorScheme={theme.dark ? 'dark' : 'light'}
33
- pointerEvents="none"
34
- seedColor={theme.materialSeed}
35
- style={StyleSheet.absoluteFill}
36
- >
37
- <NativeCountdown
38
- color={theme.color(color)}
39
- endsAt={endsAtMs}
40
- fontFamily={style.fontFamily}
41
- fontSize={style.fontSize * scale}
42
- fontWeight={style.weight}
43
- lineHeight={style.lineHeight * scale}
44
- units={resolvedUnits}
45
- />
46
- </Host>
47
- </CountdownFrame>
48
- )
49
- }
@@ -1,46 +0,0 @@
1
- import {Host} from '@expo/ui/swift-ui'
2
- import {Countdown as NativeCountdown} from '@hzblj/kara-native/swift-ui'
3
- import {MAX_FONT_SCALE, useAppTheme} from '@hzblj/kara-theme'
4
- import {StyleSheet, useWindowDimensions} from 'react-native'
5
- import type {CountdownComponent, CountdownProps} from './countdown.types'
6
- import {CountdownFrame} from './countdown-frame'
7
- import {toEpochMs, useCountdownUnits} from './countdown-units'
8
-
9
- export const Countdown: CountdownComponent = ({
10
- color = 'content.primary',
11
- endsAt,
12
- locale,
13
- testID,
14
- units,
15
- variant = 'body',
16
- }: CountdownProps) => {
17
- const theme = useAppTheme()
18
- const {fontScale} = useWindowDimensions()
19
- const resolvedUnits = useCountdownUnits(locale, units)
20
- const endsAtMs = toEpochMs(endsAt)
21
- const style = theme.typography[variant]
22
-
23
- /** SwiftUI is handed points, so it is scaled here exactly as React Native scales the frame text under it. */
24
- const scale = Math.min(fontScale, MAX_FONT_SCALE)
25
-
26
- return (
27
- <CountdownFrame endsAt={endsAtMs} testID={testID} units={resolvedUnits} variant={variant}>
28
- <Host
29
- colorScheme={theme.dark ? 'dark' : 'light'}
30
- ignoreSafeArea="all"
31
- pointerEvents="none"
32
- style={StyleSheet.absoluteFill}
33
- >
34
- <NativeCountdown
35
- color={theme.color(color)}
36
- endsAt={endsAtMs}
37
- fontFamily={style.fontFamily}
38
- fontSize={style.fontSize * scale}
39
- fontWeight={style.weight}
40
- lineHeight={style.lineHeight * scale}
41
- units={resolvedUnits}
42
- />
43
- </Host>
44
- </CountdownFrame>
45
- )
46
- }
@@ -1,24 +0,0 @@
1
- import type {CountdownUnits} from '@hzblj/kara-native'
2
- import type {ColorToken, TextVariant} from '@hzblj/kara-theme'
3
- import type {ReactNode} from 'react'
4
-
5
- export type {CountdownUnits} from '@hzblj/kara-native'
6
-
7
- /**
8
- * A live countdown — `2d / 05h / 09m / 41s` — drawn natively, so it ticks and rolls its digits without a React render
9
- * per second. The React Native tier: it sizes itself with an invisible React Native text of the same shape, and lays
10
- * its own native host over that, so it needs no `Screen` or `Island` around it.
11
- */
12
- export type CountdownProps = {
13
- color?: ColorToken
14
- /** The moment it reaches zero, as a `Date` or epoch milliseconds. It stops there. */
15
- endsAt: Date | number
16
- /** Picks the unit initials when `units` is not given. The device's locale by default. */
17
- locale?: string
18
- testID?: string
19
- /** Overrides the locale's unit initials. */
20
- units?: CountdownUnits
21
- variant?: TextVariant
22
- }
23
-
24
- export type CountdownComponent = (props: CountdownProps) => ReactNode
@@ -1,2 +0,0 @@
1
- export * from './countdown'
2
- export * from './countdown.types'
@@ -1,26 +0,0 @@
1
- import {DateTimePicker} from '@expo/ui/community/datetime-picker'
2
- import {useCallback} from 'react'
3
-
4
- import type {DatePickerComponent, DatePickerProps} from './date-picker.types'
5
-
6
- export const DatePicker: DatePickerComponent = ({
7
- max,
8
- min,
9
- mode = 'date',
10
- onValueChange,
11
- testID,
12
- value,
13
- }: DatePickerProps) => {
14
- const onChange = useCallback(
15
- (_event: unknown, date?: Date) => {
16
- if (date) {
17
- onValueChange?.(date)
18
- }
19
- },
20
- [onValueChange]
21
- )
22
-
23
- return (
24
- <DateTimePicker maximumDate={max} minimumDate={min} mode={mode} onChange={onChange} testID={testID} value={value} />
25
- )
26
- }
@@ -1,53 +0,0 @@
1
- import type {IconName} from './icon.types'
2
-
3
- export const SF_SYMBOLS = {
4
- add: 'plus',
5
- arrowLeft: 'arrow.left',
6
- arrowRight: 'arrow.right',
7
- calendar: 'calendar',
8
- check: 'checkmark',
9
- chevronDown: 'chevron.down',
10
- chevronRight: 'chevron.right',
11
- close: 'xmark',
12
- error: 'exclamationmark.octagon',
13
- grid: 'square.grid.2x2',
14
- heart: 'heart',
15
- home: 'house',
16
- info: 'info.circle',
17
- lock: 'lock',
18
- more: 'ellipsis',
19
- notification: 'bell',
20
- search: 'magnifyingglass',
21
- settings: 'gearshape',
22
- share: 'square.and.arrow.up',
23
- star: 'star',
24
- trash: 'trash',
25
- user: 'person',
26
- warning: 'exclamationmark.triangle',
27
- } as const satisfies Record<IconName, string>
28
-
29
- export const MATERIAL_ICONS = {
30
- add: 'add',
31
- arrowLeft: 'arrow-back',
32
- arrowRight: 'arrow-forward',
33
- calendar: 'calendar-today',
34
- check: 'check',
35
- chevronDown: 'expand-more',
36
- chevronRight: 'chevron-right',
37
- close: 'close',
38
- error: 'error',
39
- grid: 'grid-view',
40
- heart: 'favorite',
41
- home: 'home',
42
- info: 'info',
43
- lock: 'lock',
44
- more: 'more-horiz',
45
- notification: 'notifications',
46
- search: 'search',
47
- settings: 'settings',
48
- share: 'share',
49
- star: 'star',
50
- trash: 'delete',
51
- user: 'person',
52
- warning: 'warning',
53
- } as const satisfies Record<IconName, string>
@@ -1,2 +0,0 @@
1
- export * from './rn-context-menu'
2
- export * from './rn-context-menu.types'
@@ -1,90 +0,0 @@
1
- import {Text as ComposeText, DropdownMenu, DropdownMenuItem, Host} from '@expo/ui/jetpack-compose'
2
- import {useSx} from '@hzblj/kara-sx'
3
- import {useAppTheme} from '@hzblj/kara-theme'
4
- import {useCallback, useState} from 'react'
5
- import {Pressable, StyleSheet, View} from 'react-native'
6
- import type {MenuAction} from '../menu'
7
- import type {RnContextMenuComponent, RnContextMenuExclusionProps, RnContextMenuProps} from './rn-context-menu.types'
8
-
9
- /**
10
- * The menu's anchor: a point at the trigger's bottom start, so the dropdown opens just under it. The host lies outside
11
- * the touch path — the dropdown is a popup window of its own, which takes its touches regardless.
12
- */
13
- const styles = StyleSheet.create({
14
- anchor: {bottom: 0, height: 1, left: 0, position: 'absolute', width: 1},
15
- })
16
-
17
- type MenuRowProps = {
18
- action: MenuAction
19
- onDismiss: () => void
20
- }
21
-
22
- /** A row's `onClick` closes over its own action, so memoising it needs a component per row. */
23
- const MenuRow = ({action, onDismiss}: MenuRowProps) => {
24
- const theme = useAppTheme()
25
-
26
- const onClick = useCallback(() => {
27
- onDismiss()
28
- action.onPress()
29
- }, [action, onDismiss])
30
-
31
- return (
32
- <DropdownMenuItem onClick={onClick}>
33
- <DropdownMenuItem.Text>
34
- <ComposeText
35
- color={theme.colorString(action.destructive ? 'content.destructive' : 'content.primary')}
36
- style={theme.typography.body}
37
- >
38
- {action.label}
39
- </ComposeText>
40
- </DropdownMenuItem.Text>
41
- </DropdownMenuItem>
42
- )
43
- }
44
-
45
- /**
46
- * The trigger stays a plain React Native subtree; a long press on it expands a Compose `DropdownMenu` anchored under
47
- * it. The host behind the menu mounts on the first long press and stays — a list of triggers that are only scrolled
48
- * past never builds one. `preview` and `onPreviewPress` are iOS-only: Material has no lifted preview.
49
- */
50
- const RnContextMenuRoot = ({actions, children, testID, ...props}: RnContextMenuProps) => {
51
- const {onPreviewPress: _onPreviewPress, preview: _preview, ...sx} = props
52
- const theme = useAppTheme()
53
- const style = useSx(sx)
54
- const [isHostMounted, setIsHostMounted] = useState(false)
55
- const [expanded, setExpanded] = useState(false)
56
-
57
- const open = useCallback(() => {
58
- setIsHostMounted(true)
59
- setExpanded(true)
60
- }, [])
61
-
62
- const dismiss = useCallback(() => setExpanded(false), [])
63
-
64
- return (
65
- <Pressable onLongPress={open} style={style} testID={testID}>
66
- {children}
67
- {isHostMounted ? (
68
- <Host
69
- colorScheme={theme.dark ? 'dark' : 'light'}
70
- pointerEvents="none"
71
- seedColor={theme.materialSeed}
72
- style={styles.anchor}
73
- >
74
- <DropdownMenu expanded={expanded} onDismissRequest={dismiss}>
75
- <DropdownMenu.Items>
76
- {actions.map(action => (
77
- <MenuRow action={action} key={action.label} onDismiss={dismiss} />
78
- ))}
79
- </DropdownMenu.Items>
80
- </DropdownMenu>
81
- </Host>
82
- ) : null}
83
- </Pressable>
84
- )
85
- }
86
-
87
- /** A touchable inside the trigger already claims its own long press on Android, so this only has to group. */
88
- const Exclusion = ({children}: RnContextMenuExclusionProps) => <View>{children}</View>
89
-
90
- export const RnContextMenu: RnContextMenuComponent = Object.assign(RnContextMenuRoot, {Exclusion})
@@ -1,47 +0,0 @@
1
- import {
2
- type ContextMenuItem,
3
- ContextMenu as NativeContextMenu,
4
- ContextMenuExclusion as NativeContextMenuExclusion,
5
- } from '@hzblj/kara-native/swift-ui'
6
- import {useSx} from '@hzblj/kara-sx'
7
- import {useCallback, useMemo} from 'react'
8
- import type {SFSymbol} from 'sf-symbols-typescript'
9
- import {SF_SYMBOLS} from '../../atoms/icon'
10
- import type {RnContextMenuComponent, RnContextMenuExclusionProps, RnContextMenuProps} from './rn-context-menu.types'
11
-
12
- const RnContextMenuRoot = ({actions, children, onPreviewPress, preview, testID, ...sx}: RnContextMenuProps) => {
13
- const style = useSx(sx)
14
-
15
- /** The native side hands back an id; the index is the one identity every action has. */
16
- const items = useMemo<ContextMenuItem[]>(
17
- () =>
18
- actions.map((action, index) => ({
19
- destructive: action.destructive,
20
- id: String(index),
21
- systemImage: action.icon ? (SF_SYMBOLS[action.icon] as SFSymbol) : undefined,
22
- title: action.label,
23
- })),
24
- [actions]
25
- )
26
-
27
- const onSelectItem = useCallback((id: string) => actions[Number(id)]?.onPress(), [actions])
28
-
29
- return (
30
- <NativeContextMenu
31
- items={items}
32
- onPreviewPress={onPreviewPress}
33
- onSelectItem={onSelectItem}
34
- preview={preview}
35
- style={style}
36
- testID={testID}
37
- >
38
- {children}
39
- </NativeContextMenu>
40
- )
41
- }
42
-
43
- const Exclusion = ({children}: RnContextMenuExclusionProps) => (
44
- <NativeContextMenuExclusion>{children}</NativeContextMenuExclusion>
45
- )
46
-
47
- export const RnContextMenu: RnContextMenuComponent = Object.assign(RnContextMenuRoot, {Exclusion})
@@ -1,36 +0,0 @@
1
- import type {ResponsiveViewSx} from '@hzblj/kara-sx'
2
- import type {ReactNode} from 'react'
3
- import type {MenuAction} from '../menu'
4
-
5
- /**
6
- * A context menu on a React Native subtree — a card in a list, say — opened by a long press. iOS: a
7
- * `UIContextMenuInteraction`, with an optional React Native `preview` lifted while the menu is open and a callback for
8
- * a tap on it. Android: a Material dropdown under the subtree. The React Native tier, unlike `ContextMenu`, whose
9
- * trigger is a native view inside a host.
10
- */
11
- export type RnContextMenuProps = ResponsiveViewSx & {
12
- actions: MenuAction[]
13
- /** The trigger, laid out as usual. */
14
- children: ReactNode
15
- /** iOS: the user tapped the lifted preview, committing to it — open the detail the card stands for. */
16
- onPreviewPress?: () => void
17
- /**
18
- * iOS: lifted in place of the trigger while the menu is open — a larger version of the card, say. A snapshot of the
19
- * trigger when unset. Mounted only once a finger has rested on the trigger, so a list of cards does not pay for
20
- * every preview up front.
21
- */
22
- preview?: ReactNode
23
- testID?: string
24
- }
25
-
26
- export type RnContextMenuExclusionProps = {
27
- children: ReactNode
28
- }
29
-
30
- export type RnContextMenuComponent = ((props: RnContextMenuProps) => ReactNode) & {
31
- /**
32
- * A region of the trigger that must not open the menu — a button inside the card. On iOS the interaction covers the
33
- * whole trigger, so a long press on that button would otherwise open the card's menu.
34
- */
35
- Exclusion: (props: RnContextMenuExclusionProps) => ReactNode
36
- }
File without changes