@isoline-ui/react-native 0.0.0-stage → 0.1.0-alpha.3

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 (167) hide show
  1. package/LICENSE +22 -0
  2. package/NOTICE.md +12 -0
  3. package/README.md +104 -3
  4. package/dist/amount.d.ts +3 -0
  5. package/dist/calendar.d.ts +7 -0
  6. package/dist/components/action-chip.d.ts +9 -0
  7. package/dist/components/amount-field.d.ts +12 -0
  8. package/dist/components/avatar.d.ts +17 -0
  9. package/dist/components/back-button.d.ts +12 -0
  10. package/dist/components/back-icon.d.ts +3 -0
  11. package/dist/components/badge.d.ts +13 -0
  12. package/dist/components/banner.d.ts +14 -0
  13. package/dist/components/bottom-sheet-native.d.ts +11 -0
  14. package/dist/components/bottom-sheet-web.d.ts +18 -0
  15. package/dist/components/bottom-sheet.d.ts +3 -0
  16. package/dist/components/button-group.d.ts +26 -0
  17. package/dist/components/button.d.ts +48 -0
  18. package/dist/components/checkbox.d.ts +12 -0
  19. package/dist/components/color-picker.d.ts +21 -0
  20. package/dist/components/context-menu.d.ts +26 -0
  21. package/dist/components/date-picker.d.ts +64 -0
  22. package/dist/components/dialog-context.d.ts +2 -0
  23. package/dist/components/dialog.d.ts +20 -0
  24. package/dist/components/empty-state.d.ts +15 -0
  25. package/dist/components/expand-indicator.d.ts +27 -0
  26. package/dist/components/expressive-shape.d.ts +18 -0
  27. package/dist/components/fab-menu-legacy.d.ts +31 -0
  28. package/dist/components/fab-menu.d.ts +21 -0
  29. package/dist/components/filter-chip.d.ts +69 -0
  30. package/dist/components/floating-action-button.d.ts +15 -0
  31. package/dist/components/icon-button.d.ts +40 -0
  32. package/dist/components/icons.d.ts +18 -0
  33. package/dist/components/input.d.ts +9 -0
  34. package/dist/components/keyboard-inset-view.d.ts +15 -0
  35. package/dist/components/list-controls.d.ts +38 -0
  36. package/dist/components/loading-indicator.d.ts +9 -0
  37. package/dist/components/menu-sheet.d.ts +36 -0
  38. package/dist/components/panel-sheet.d.ts +8 -0
  39. package/dist/components/password-visibility.d.ts +5 -0
  40. package/dist/components/popover-layout.d.ts +25 -0
  41. package/dist/components/popover.d.ts +20 -0
  42. package/dist/components/pressable.d.ts +12 -0
  43. package/dist/components/radio-navigation.d.ts +11 -0
  44. package/dist/components/refresh-control.d.ts +16 -0
  45. package/dist/components/row-gap.d.ts +9 -0
  46. package/dist/components/search-field.d.ts +22 -0
  47. package/dist/components/select-field.d.ts +16 -0
  48. package/dist/components/settings-list.d.ts +98 -0
  49. package/dist/components/settings-row.d.ts +13 -0
  50. package/dist/components/sheet-frame.d.ts +27 -0
  51. package/dist/components/sheet-lifecycle.d.ts +36 -0
  52. package/dist/components/sheet-transition.d.ts +36 -0
  53. package/dist/components/slider.d.ts +21 -0
  54. package/dist/components/snackbar.d.ts +10 -0
  55. package/dist/components/split-button.d.ts +38 -0
  56. package/dist/components/squish.d.ts +20 -0
  57. package/dist/components/swipeable-row.d.ts +20 -0
  58. package/dist/components/switch.d.ts +8 -0
  59. package/dist/components/tab-bar.d.ts +20 -0
  60. package/dist/components/text-field.d.ts +31 -0
  61. package/dist/components/text.d.ts +9 -0
  62. package/dist/components/time-picker.d.ts +40 -0
  63. package/dist/components/toolbar.d.ts +77 -0
  64. package/dist/components/top-bar.d.ts +27 -0
  65. package/dist/components/undo-bar.d.ts +23 -0
  66. package/dist/components/wavy-progress.d.ts +21 -0
  67. package/dist/components/zoomable-image.d.ts +18 -0
  68. package/dist/expressive-shapes.d.ts +8 -0
  69. package/dist/index.d.ts +62 -0
  70. package/dist/keyboard.d.ts +2 -0
  71. package/dist/material.d.ts +11 -0
  72. package/dist/media.d.ts +11 -0
  73. package/dist/motion.d.ts +96 -0
  74. package/dist/platform.d.ts +3 -0
  75. package/dist/preset-tokens.d.ts +264 -0
  76. package/dist/provider.d.ts +59 -0
  77. package/dist/reduced-motion.d.ts +45 -0
  78. package/dist/selection.d.ts +2 -0
  79. package/dist/shape.d.ts +99 -0
  80. package/dist/sheet-physics.d.ts +37 -0
  81. package/dist/theme.d.ts +103 -0
  82. package/dist/tokens.d.ts +2 -0
  83. package/dist/use-keyboard-height.d.ts +10 -0
  84. package/dist/utils.d.ts +3 -0
  85. package/nativewind.cjs +68 -0
  86. package/package.json +63 -6
  87. package/src/amount.ts +53 -0
  88. package/src/calendar.ts +42 -0
  89. package/src/components/action-chip.tsx +64 -0
  90. package/src/components/amount-field.tsx +49 -0
  91. package/src/components/avatar.tsx +91 -0
  92. package/src/components/back-button.tsx +39 -0
  93. package/src/components/back-icon.tsx +25 -0
  94. package/src/components/badge.tsx +41 -0
  95. package/src/components/banner.tsx +63 -0
  96. package/src/components/bottom-sheet-native.tsx +52 -0
  97. package/src/components/bottom-sheet-web.tsx +193 -0
  98. package/src/components/bottom-sheet.tsx +5 -0
  99. package/src/components/button-group.tsx +326 -0
  100. package/src/components/button.tsx +337 -0
  101. package/src/components/checkbox.tsx +71 -0
  102. package/src/components/color-picker.tsx +115 -0
  103. package/src/components/context-menu.tsx +149 -0
  104. package/src/components/date-picker.tsx +466 -0
  105. package/src/components/dialog-context.ts +3 -0
  106. package/src/components/dialog.tsx +210 -0
  107. package/src/components/empty-state.tsx +49 -0
  108. package/src/components/expand-indicator.tsx +60 -0
  109. package/src/components/expressive-shape.tsx +37 -0
  110. package/src/components/fab-menu-legacy.tsx +181 -0
  111. package/src/components/fab-menu.tsx +90 -0
  112. package/src/components/filter-chip.tsx +136 -0
  113. package/src/components/floating-action-button.tsx +79 -0
  114. package/src/components/icon-button.tsx +127 -0
  115. package/src/components/icons.tsx +43 -0
  116. package/src/components/input.tsx +80 -0
  117. package/src/components/keyboard-inset-view.tsx +28 -0
  118. package/src/components/list-controls.tsx +134 -0
  119. package/src/components/loading-indicator.tsx +183 -0
  120. package/src/components/menu-sheet.tsx +133 -0
  121. package/src/components/panel-sheet.tsx +90 -0
  122. package/src/components/password-visibility.tsx +39 -0
  123. package/src/components/popover-layout.ts +47 -0
  124. package/src/components/popover.tsx +139 -0
  125. package/src/components/pressable.tsx +108 -0
  126. package/src/components/radio-navigation.ts +52 -0
  127. package/src/components/refresh-control.tsx +39 -0
  128. package/src/components/row-gap.tsx +13 -0
  129. package/src/components/search-field.tsx +92 -0
  130. package/src/components/select-field.tsx +105 -0
  131. package/src/components/settings-list.tsx +302 -0
  132. package/src/components/settings-row.tsx +61 -0
  133. package/src/components/sheet-frame.tsx +250 -0
  134. package/src/components/sheet-lifecycle.ts +233 -0
  135. package/src/components/sheet-transition.ts +105 -0
  136. package/src/components/slider.tsx +185 -0
  137. package/src/components/snackbar.tsx +97 -0
  138. package/src/components/split-button.tsx +212 -0
  139. package/src/components/squish.ts +41 -0
  140. package/src/components/swipeable-row.tsx +109 -0
  141. package/src/components/switch.tsx +77 -0
  142. package/src/components/tab-bar.tsx +195 -0
  143. package/src/components/text-field.tsx +264 -0
  144. package/src/components/text.tsx +58 -0
  145. package/src/components/time-picker.tsx +277 -0
  146. package/src/components/toolbar.tsx +406 -0
  147. package/src/components/top-bar.tsx +91 -0
  148. package/src/components/undo-bar.tsx +137 -0
  149. package/src/components/wavy-progress.tsx +150 -0
  150. package/src/components/zoomable-image.tsx +240 -0
  151. package/src/expressive-shapes.ts +25 -0
  152. package/src/index.ts +62 -0
  153. package/src/keyboard.ts +5 -0
  154. package/src/material.ts +16 -0
  155. package/src/media.ts +51 -0
  156. package/src/motion.ts +96 -0
  157. package/src/platform.ts +13 -0
  158. package/src/preset-tokens.ts +265 -0
  159. package/src/provider.tsx +201 -0
  160. package/src/reduced-motion.ts +50 -0
  161. package/src/selection.ts +11 -0
  162. package/src/shape.ts +217 -0
  163. package/src/sheet-physics.ts +73 -0
  164. package/src/theme.ts +180 -0
  165. package/src/tokens.ts +2 -0
  166. package/src/use-keyboard-height.ts +64 -0
  167. package/src/utils.ts +7 -0
package/src/theme.ts ADDED
@@ -0,0 +1,180 @@
1
+ import { presetTokens } from './preset-tokens.ts';
2
+
3
+ export type ColorScheme = 'light' | 'dark';
4
+ export type TextVariant =
5
+ | 'default'
6
+ | 'display'
7
+ | 'title'
8
+ | 'heading'
9
+ | 'subject'
10
+ | 'body'
11
+ | 'reader'
12
+ | 'muted'
13
+ | 'caption'
14
+ | 'labelLarge'
15
+ | 'label'
16
+ | 'mono'
17
+ | 'value'
18
+ | 'valueLarge'
19
+ | 'meta';
20
+
21
+ export interface TypographyRole {
22
+ fontFamily?: string;
23
+ fontSize?: number;
24
+ lineHeight?: number;
25
+ fontWeight?: '400' | '500' | '600' | '700' | '800';
26
+ letterSpacing?: number;
27
+ textTransform?: 'none' | 'uppercase' | 'lowercase' | 'capitalize';
28
+ includeFontPadding?: boolean;
29
+ }
30
+
31
+ export interface IsolineTheme {
32
+ name: string;
33
+ scheme: ColorScheme;
34
+ /** HSL channel triplets, e.g. "268 75% 82%", matching the NativeWind preset. */
35
+ tokens: Readonly<Record<string, string>>;
36
+ typography?: Partial<Record<TextVariant, TypographyRole>>;
37
+ sheetGesture?: 'handle' | 'panel';
38
+ }
39
+
40
+ export const ROLE_VARIABLES = {
41
+ primary: '--primary',
42
+ onPrimary: '--primary-foreground',
43
+ primaryContainer: '--primary-container',
44
+ onPrimaryContainer: '--on-primary-container',
45
+ secondaryContainer: '--secondary-container',
46
+ onSecondaryContainer: '--on-secondary-container',
47
+ tertiary: '--tertiary',
48
+ onTertiary: '--on-tertiary',
49
+ tertiaryContainer: '--tertiary-container',
50
+ onTertiaryContainer: '--on-tertiary-container',
51
+ error: '--destructive',
52
+ onError: '--destructive-foreground',
53
+ errorContainer: '--error-container',
54
+ onErrorContainer: '--on-error-container',
55
+ surface: '--surface',
56
+ onSurface: '--on-surface',
57
+ surfaceVariant: '--surface-variant',
58
+ onSurfaceVariant: '--on-surface-variant',
59
+ surfaceContainerLowest: '--surface-container-lowest',
60
+ surfaceContainerLow: '--surface-container-low',
61
+ surfaceContainer: '--surface-container',
62
+ surfaceContainerHigh: '--surface-container-high',
63
+ surfaceContainerHighest: '--surface-container-highest',
64
+ outline: '--outline',
65
+ outlineVariant: '--outline-variant',
66
+ inverseSurface: '--inverse-surface',
67
+ inverseOnSurface: '--inverse-on-surface',
68
+ inversePrimary: '--inverse-primary',
69
+ success: '--success',
70
+ } as const;
71
+
72
+ export type ThemeColors = { -readonly [K in keyof typeof ROLE_VARIABLES]: string };
73
+ export interface RippleColors {
74
+ onNeutral: string;
75
+ onTonal: string;
76
+ }
77
+
78
+ export function resolveThemeColors(theme: IsolineTheme): ThemeColors {
79
+ const result = {} as ThemeColors;
80
+ for (const [role, variable] of Object.entries(ROLE_VARIABLES)) {
81
+ const channels = theme.tokens[variable];
82
+ if (!channels) throw new Error('Isoline UI: missing theme token ' + variable);
83
+ result[role as keyof ThemeColors] = 'hsl(' + channels + ')';
84
+ }
85
+ return result;
86
+ }
87
+
88
+ export function rippleColors(scheme: ColorScheme): RippleColors {
89
+ const light = 'rgba(255, 255, 255, 0.12)';
90
+ const dark = 'rgba(0, 0, 0, 0.12)';
91
+ return scheme === 'light'
92
+ ? { onNeutral: dark, onTonal: light }
93
+ : { onNeutral: light, onTonal: dark };
94
+ }
95
+
96
+ /** Creates a new theme without mutating the reusable preset. */
97
+ export function extendTheme(
98
+ base: IsolineTheme,
99
+ overrides: {
100
+ name?: string;
101
+ tokens?: Readonly<Record<string, string>>;
102
+ typography?: IsolineTheme['typography'];
103
+ sheetGesture?: IsolineTheme['sheetGesture'];
104
+ },
105
+ ): IsolineTheme {
106
+ const typography = { ...base.typography };
107
+ for (const [role, patch] of Object.entries(overrides.typography ?? {})) {
108
+ const variant = role as TextVariant;
109
+ typography[variant] = { ...typography[variant], ...patch };
110
+ }
111
+ return {
112
+ ...base,
113
+ name: overrides.name ?? base.name,
114
+ tokens: { ...base.tokens, ...overrides.tokens },
115
+ typography,
116
+ sheetGesture: overrides.sheetGesture ?? base.sheetGesture,
117
+ };
118
+ }
119
+
120
+ function preset(name: keyof typeof presetTokens, scheme: ColorScheme): IsolineTheme {
121
+ // Fill optional roles absent from an older consumer palette, keeping a complete contract.
122
+ const theme: IsolineTheme = {
123
+ name,
124
+ scheme,
125
+ tokens: { ...presetTokens.violet[scheme], ...presetTokens[name][scheme] },
126
+ };
127
+ if (name === 'teal')
128
+ theme.typography = {
129
+ label: { textTransform: 'uppercase', fontWeight: '700', letterSpacing: 1.12 },
130
+ };
131
+ if (name === 'cyan') {
132
+ theme.sheetGesture = 'panel';
133
+ theme.typography = {
134
+ display: {
135
+ fontFamily: 'SpaceGrotesk_700Bold',
136
+ fontWeight: '700',
137
+ fontSize: 44,
138
+ lineHeight: 48,
139
+ letterSpacing: -1.32,
140
+ },
141
+ title: {
142
+ fontFamily: 'SpaceGrotesk_700Bold',
143
+ fontWeight: '700',
144
+ lineHeight: 38,
145
+ letterSpacing: -0.64,
146
+ },
147
+ heading: { fontFamily: 'SpaceGrotesk_700Bold', fontWeight: '700', letterSpacing: -0.22 },
148
+ label: { letterSpacing: 0 },
149
+ mono: { fontFamily: 'JetBrainsMono_500Medium', fontSize: 13 },
150
+ value: { fontFamily: 'JetBrainsMono_500Medium' },
151
+ valueLarge: { fontFamily: 'JetBrainsMono_700Bold', letterSpacing: -0.22 },
152
+ };
153
+ }
154
+ return theme;
155
+ }
156
+
157
+ const violet = { light: preset('violet', 'light'), dark: preset('violet', 'dark') };
158
+ const teal = { light: preset('teal', 'light'), dark: preset('teal', 'dark') };
159
+ const cyan = { light: preset('cyan', 'light'), dark: preset('cyan', 'dark') };
160
+
161
+ export const themes = {
162
+ violet,
163
+ teal,
164
+ cyan,
165
+ /** @deprecated Use themes.violet. */
166
+ inboki: violet,
167
+ /** @deprecated Use themes.teal. */
168
+ yours: teal,
169
+ /** @deprecated Use themes.cyan. */
170
+ atlas: cyan,
171
+ } as const;
172
+
173
+ export const elevation = {
174
+ level0: 0,
175
+ level1: 1,
176
+ level2: 3,
177
+ level3: 6,
178
+ level4: 8,
179
+ level5: 12,
180
+ } as const;
package/src/tokens.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './shape.ts';
2
+ export * from './motion.ts';
@@ -0,0 +1,64 @@
1
+ import * as React from 'react';
2
+ import { Keyboard, Platform } from 'react-native';
3
+ import { keyboardOcclusion } from './keyboard.ts';
4
+
5
+ /**
6
+ * The on-screen keyboard's height in dp, or 0 while it is closed.
7
+ *
8
+ * Anything pinned to the bottom of the screen has to measure the keyboard
9
+ * itself: `KeyboardAvoidingView` shrinks the layout it wraps, which does
10
+ * nothing for a view positioned against the window's bottom edge, and
11
+ * Android's own `adjustResize` stops resizing the window at all once the
12
+ * app is edge-to-edge.
13
+ */
14
+ export function useKeyboardHeight(): number {
15
+ // Seeded from the keyboard's current metrics rather than starting at zero:
16
+ // listeners only report what happens next, so anything mounted while the
17
+ // keyboard is already open would sit at the bottom of the screen — behind
18
+ // the keyboard — until the next time it happened to open.
19
+ const [height, setHeight] = React.useState(() => Platform.OS === 'web' ? 0 : Keyboard.metrics?.()?.height ?? 0);
20
+
21
+ React.useEffect(() => {
22
+ if (Platform.OS === 'web') {
23
+ const viewport = window.visualViewport;
24
+ let frame = 0;
25
+ const update = () => {
26
+ const element = document.activeElement;
27
+ const editing = element instanceof HTMLElement && (['INPUT','TEXTAREA'].includes(element.tagName) || element.isContentEditable);
28
+ setHeight(keyboardOcclusion(window.innerHeight, viewport?.height ?? window.innerHeight, viewport?.offsetTop ?? 0, viewport?.scale ?? 1, editing));
29
+ };
30
+ const focus = () => { cancelAnimationFrame(frame); frame = requestAnimationFrame(update); };
31
+ update();
32
+ viewport?.addEventListener('resize', update);
33
+ viewport?.addEventListener('scroll', update);
34
+ window.addEventListener('resize', update);
35
+ document.addEventListener('focusin', focus);
36
+ document.addEventListener('focusout', focus);
37
+ return () => {
38
+ cancelAnimationFrame(frame);
39
+ viewport?.removeEventListener('resize', update);
40
+ viewport?.removeEventListener('scroll', update);
41
+ window.removeEventListener('resize', update);
42
+ document.removeEventListener('focusin', focus);
43
+ document.removeEventListener('focusout', focus);
44
+ };
45
+ }
46
+ // iOS gets the `will` pair so a view travels with the keyboard rather
47
+ // than jumping after it; Android only ever emits the `did` pair.
48
+ const shown = Keyboard.addListener(
49
+ Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow',
50
+ (event) => setHeight(event.endCoordinates.height),
51
+ );
52
+ const hidden = Keyboard.addListener(
53
+ Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide',
54
+ () => setHeight(0),
55
+ );
56
+
57
+ return () => {
58
+ shown.remove();
59
+ hidden.remove();
60
+ };
61
+ }, []);
62
+
63
+ return height;
64
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ /** Merges Tailwind classes so later conditional classes win over earlier ones. */
5
+ export function cn(...inputs: ClassValue[]): string {
6
+ return twMerge(clsx(inputs));
7
+ }